If you don't provide a copy constructor, the compiler gives you a default one which performs a member-wise copy. Under most circumstances the default one is quite sufficient and therefore you don't need to write your own. The exception is when member-wise copy isn't sufficient. For example, in the following poorly designed/coded class:
1 2 3 4 5 6 7 8
|
class Foo {
public:
Foo( int x, const char* s ) : x( x ), str( strdup( s ) ) {}
~Foo() { free( str ); }
private:
int x;
char* str;
};
|
the default copy constructor will simply copy the pointer, leaving two objects with the same pointer. When one object is destroyed, the memory is freed, leaving a dangling pointer in the other object.
To fix the above problem, you need to write your own copy constructor:
1 2 3 4 5 6 7 8 9
|
class Foo {
public:
Foo( int x, const char* s ) : x( x ), str( strdup( s ) ) {}
Foo( const Foo& f ): x( f.x ), str( strdup( f.str ) ) {}
~Foo() { free( str ); }
private:
int x;
char* str;
};
|
But copy construction and assignment are _very_ similar, so any time you need to write your own copy constructor you will also need to write your own assignment operator, as it suffers from similar problems (the default assignment operator is again member-wise copy):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
class Foo {
public:
Foo( int x, const char* s ) : x( x ), str( strdup( s ) ) {}
Foo( const Foo& f ): x( f.x ), str( strdup( f.str ) ) {}
Foo& operator=( const Foo& rhs ) {
x = rhs.x;
str = strdup( rhs.str );
return *this;
}
~Foo() { free( str ); }
private:
int x;
char* str;
};
|
[Note: the above is nowhere close to exception safe, but that's another topic.]
Constructors should be written such that data members are initialized in the initializer list instead of in the main body of the function.
1 2 3 4 5
|
Foo::Foo( const Foo& f )
: // The colon begins the initializer list
x( f.x ), // Assign each member of the class in the order they are declared
str( strdup( f.str ) )
{} // Empty function body
|
In this particular case, the above is equivalent to
1 2 3 4 5
|
Foo::Foo( const Foo& f )
{
x = f.x;
str = strdup( f.str );
}
|
however in the general case this is not always true. Use initializer lists when at all possible; there is never a disadvantage to using them, but there are disadvantages to not using them.
You need to understand initializer lists when writing constructors for derived types, because the derived type needs to call a constructor on the base type before it does anything else.
1 2 3 4 5 6 7 8 9 10 11 12 13
|
class Base {
public:
Base( int x ) : x( x ) {}
private:
int x;
};
class Derived : public Base {
public:
Derived( int z, float f ) : Base( z ), f( f ) {}
private:
float f;
};
|
Note the constructor of Derived: the syntax Base( z ) says to call Base's constructor that takes an int and pass the value of z to it. This is the _only_ way to write Derived's constructor.