C++ Common Knowledge: Assignment and Initialization Are Different
Initialization and assignment are different operations, with different uses and different implementations.
Let's get it absolutely straight. Assignment occurs when you assign. All the other copying you run into is initialization, including initialization in a declaration, function return, argument passing, and catching exceptions.
Assignment and initialization are essentially different operations not only because they're used in different contexts but also because they do different things. This difference in operation is not so obvious in the built-in types such as int or double, because, in that case, both assignment and initialization consist simply of copying some bits (but see also References Are Aliases, Not Pointers [5, 13]):
int a = 12; // initialization, copy 0X000C to a a = 12; // assignment, copy 0X000C to a
However, things can be quite different for user-defined types. Consider the following simple, nonstandard string class:
class String { public: String( const char *init ); // intentionally not explicit! ~String(); String( const String &that ); String &operator =( const String &that ); String &operator =( const char *str ); void swap( String &that ); friend const String // concatenate operator +( const String &, const String & ); friend bool operator <( const String &, const String & ); //... private: String( const char *, const char * ); // computational char *s_; };
Initializing a String object with a character string is straightforward. We allocate a buffer big enough to hold a copy of the character string and then copy.
String::String( const char *init ) { if( !init ) init = ""; s_ = new char[ strlen(init)+1 ]; strcpy( s_, init ); }
The destructor does what it does:
String::~String() { delete [] s_; }
Assignment is a somewhat more difficult job than construction:
String &String::operator =( const char *str ) { if( !str ) str = ""; char *tmp = strcpy( new char[ strlen(str)+1 ], str ); delete [] s_; s_ = tmp; return *this; }
An assignment is somewhat like destruction followed by a construction. For a complex user-defined type, the target (left side, or this) must be cleaned up before it is reinitialized with the source (right side, or str). In the case of our String type, the String's existing character buffer must be freed before a new character buffer is attached. See Exception Safe Functions [39, 135] for an explanation of the ordering of the statements. (By the way, just about every week somebody reinvents the bright idea of implementing assignment with an explicit destructor call and using placement new to call a constructor. It doesn't always work, and it's not exception safe. Don't do it.)
Because a proper assignment operation cleans up its left argument, one should never perform a user-defined assignment on uninitialized storage:
String *names = static_cast<String *>(::operator new( BUFSIZ )); names[0] = "Sakamoto"; // oops! delete [] uninitialized pointer!
In this case, names refers to uninitialized storage because we called operator new directly, avoiding implicit initialization by String's default constructor; names refers to a hunk of memory filled with random bits. When the String assignment operator is called in the second line, it will attempt to perform an array delete on an uninitialized pointer. (See Placement New [35, 119] for a safe way to perform an operation similar to such an assignment.)
Because a constructor has less work to do than an assignment operator (in that a constructor can assume it's working with uninitialized storage), an implementation will sometimes employ what's known as a "computational constructor" for efficiency:
const String operator +( const String &a, const String &b ) { return String( a.s_, b.s_ ); }
The two-argument computational constructor is not intended to be part of the interface of the String class, so it's declared to be private.
String::String( const char *a, const char *b ) { s_ = new char[ strlen(a)+strlen(b)+1 ]; strcat( strcpy( s_, a ), b ); }