Ruminations on the standard C++ library — Dynamic arrays II—To copy or not to copy
Several weeks had passed and another assignment faced me with its deadline… somehow I always managed never to get around to doing it until the very last moment, which was, indeed, close to midnight this Thursday evening. Yearning for my bed I was struggling with storing structures in a vector. Somehow, things just seemed to go wrong no matter what I did.
My code… somehow convoluted as always… looked sort of like this:
#include <vector> #include <iostream> #include <cstring> using namespace std; struct A { char* c; A(char* str) { c = new char[strlen(str) + 1]; strcpy(c, str); } ~A() { if (c) free(c); } }; std::vector<A> myvec; void test() { A a((char*)"Test"); myvec.push_back(a); } int main() { test(); for (int i = 0; i < myvec.size(); ++i) std::cout << myvec[i].c << std::endl; return 0; }
This, however, merely produced a series of strange characters on my screen… not Test, as I would presume. ‘What is wrong with this thing?’ I exclaimed loudly. A girl sitting a few computers away tried to suppress a wry smile, but it did not elude my perception. This, of course, did not help on my frustrations.
After having spent a few more moments staring at my screen I noticed the girl stood up and walked over toward me. ‘Hey there. Need any help?’ she smiled cheerily.
I looked up thankfully… my code could use a fresh pair of eyes just about now. ‘Yes, please. If you have a few moments to spare?’
‘So, what’s wrong?’ she smirked, peering at my code.
I explained my dilemma to her.
‘Hmm… that would mean your character string, c points to some random place in the memory. That is, of course, a bad thing.’ she smiled shortly, then the slight frown appeared again as she looked at the code again. ‘I think you should try to move the contents of the test function into main and then try again. Like this…’
int main() { A a((char*)"Test"); myvec.push_back(a); for (int i = 0; i < myvec.size(); ++i) std::cout << myvec[i].c << std::endl; return 0; }
At that, a book slammed shut behind us and we both gave a start. ‘It is not what is written, it is what has not been written that is the bug here’, a voice said behind us.
As I swirled around on my chair, almost cutting of the girl’s legs in the progress, it was the robed figure again. What was he doing, stalking around the computer labs close to midnight? My rash demand on why he dared sneak up on us turned into an extremely clever ‘Uh… huh?’
‘Needing to have the object in the exact same scope of the use of it isn’t really too useful. We would like to be able to use the vector in any place through the program, right?’ He looked at each of us in turn.
The girl shrugged. ‘If it works, what does it matter?’
‘If we can program without functions does that mean functions are useless?’ he shook his head. ‘First things first… What on earth is that abomination right there?’ he pointed to this part of the code:
A a((char*)"Test");
‘Yea… what about it? The constructor takes a char* so it complained about losing const-qualifiers so I just cast it. Should work, shouldn’t it?’ I looked up at his face, but it seemed to be hiding in the depths of his hood.
Shaking his head he began to chant, ‘A string literal can be converted to a constant pointer to a char so long as it’s a non-wide string literal [1]. The conversion to a pointer to a char is deprecated as stated by the Holy Standard, chapter 4, verse 2:2.’
Blinking a few times, I tried to decipher his words… ‘So are you saying that type cast is illegal?’
He nodded. ‘Do away with it, swiftly!’ he gestured in a sweeping movement with his hand.
I changed the line to look like this:
A a("Test");
Nodding he pointed to the constructor of A. ‘So instead of accepting a char*, which, by the way, insinuates that the constructor will potentially change the string you pass, you want it to accept a constant string literal, id est a string literal you do not intend to change.’
Muttering inside I changed the constructor to the following:
A(const char* str) { c = new char[strlen(str) + 1]; strcpy(c, str); }
Obviously still not satisfied the robed guy pointed at the line:
free(c);
‘Using free together with new[]? You do not know whether the C and the C++ memory management routines are handled in the same manner. With malloc goes free. With new goes delete. With new[] goes delete[]. Keep these in mind whenever you are managing memory on the heap.’
I quickly corrected the destructor to mimic his instructions.
~A() { if (c) delete[] c; }
Nodding, seemingly to himself, the robed guy looked at the girl. ‘So, Sarah… if the solution isn’t to move the insertion into the main loop, what would it be?’
‘Uhm…’ she faltered, almost as intelligent as my own uhm’s. I smiled to myself. She continued, ‘When test() finishes then a goes out of scope…’
I cheered up, finally something I seemingly understood, ‘At which point it calls the destructor!’
He nodded. ‘Which causes?’
‘Well if I wasn’t interrupted!’ Sarah cast an indignantly glance at me, ‘I was saying that when it is inserted into the vector, a copy is made of the contents of the vector, because it’s stored by value. And, when it leaves scope the destructor is called.’ she nodded and crossed her arms.
‘Right, and because it’s copied then it should work, even though a goes out of scope.’ I said triumphantly.
At that a broad smile appeared in the depths of the hood of the robed man, ‘And pray tell me, how does it know that it needs to copy the entire string literal into the new object? The compiler cannot guess at what you’re doing, you need to instruct it to copy the string using… a copy constructor.’
Taking control of my keyboard, he continued. ‘So without writing it for yourself your C++ compiler adds an invisible copy constructor that makes your class look like this…’
struct A { char* c; A(char* str) { c = new char[strlen(str) + 1]; strcpy(c, str); } A(const A& copy) { c = copy.c; } ~A() { if (c) free(c); } };
‘But that only copies the pointer to the string literal.’ Sarah picked up, ‘and thus when a goes out of scope in the test-function then the contents of the vector will point to an invalid location in memory.’
The robed man merely nodded.
Starting to slowly get what was wrong I decided to try my luck. ‘So I would want to modify the copy constructor to create a copy of the string. Like this…’
A(const A& copy) { c = new char[strlen(copy.c) + 1]; strcpy(c, copy.c); }
I looked up to see whether I was right.
The robed man nodded. ‘Yes, indeed, that will print your “Test”. So, you should use the rule of three: when you define a constructor you almost always want to define a copy constructor and a destructor as well. Now as for your code it works now, but whether it’s good C++ style is another matter…’
I looked back at the screen to try to figure out what was not good C++ style, but I came up blank. I turned around to ask him, but he was already gone. I gave a slight shiver.
‘You get used to him, I guess… or stop coming here in the late hours of the day.’ Sarah smiled at me. ‘I’d better be finishing up my paper now. Good luck.’ She turned and left for her seat.
‘Thanks.’ I mumbled, and dug into what was left of the assignment.
References
Sub-Pages
Categories
Archives
- November 2009
- October 2009
- June 2009
- May 2009
- February 2009
- January 2009
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- December 2006
- November 2006
- October 2006
- September 2006
- June 2006
- April 2006
- March 2006
- February 2006
- January 2006
- December 2005
- November 2005
- October 2005
- September 2005
- May 2005
- April 2005
- March 2005
- February 2005
- January 2005
- December 2004
- June 2004
- April 2004
- February 2004
- November 2003
- January 2003
- November 2002