Destructor Copy Constructor And Assignment Operator Definition

Constructor, destructors and assignment operators

There is a rule of thumb in C++ that if a class defines a destructor, constructor and copy assignment operator – then it should explicitly define these and not rely on their default implementation.

Why do we need them?

In the example below, the absence of an explicit copy constructor will simply make an exact copy of the class and you end up with two classes pointing to the same memory address – not what you want.  When you delete the array pointer in one class for example, the other class will be pointing to memory to which you have no idea as what it contains.

Consider the following example class which is used to house an array of integers plus its size. It is written in such a way as to guarantee a crash:

#include ,algorithm> class Array { private: int size; int* vals; public: ~Array(); Array( int s, int* v ); }; Array::~Array() { delete vals; vals = NULL; } Array::Array( int s, int* v ) { size = s; vals = new int[ size ]; std::copy( v, v + size, vals ); } int main() { int vals[ 4 ] = { 1, 2, 3, 4 }; Array a1( 4, vals ); Array a2( a1 ); return 0; } // Bam!

Once the program goes out of scope (exits the main loop), the class destructor is called. Twice. Once for Array object a1 and then for a2. Remember that the compiler-generated default copy constructor simply makes a copy of the pointer vals.  It does not know that it also needs to allocate memory for a new vals.  When a1 is deleted, its destructor frees up vals.  Subsequent use of vals in the other instance a2 when once more trying to delete it in the destructor causes the program to fall over, since that instance of vals does not exist any more.

Using explicitly defined copy constructors

There is clearly a need to explicitly define a copy constructor that correctly copies vals.  Suppose someone has now defined an acceptable copy constructor, in addition to the default constructor:

class Array { private: int size; int* vals; public: ~Array(); Array( int s, int* v ); Array( const Array& a ); }; Array::Array( const Array &a ) { size = a.size; vals = new int[ a.size ]; std::copy( a.vals, a.vals + size, vals ); }

This improves the situation in that the code will not crash when exiting the main loop. But we’re not in the clear yet. It is still vulnerable to a crash as soon as someone has a go at assigning an instance of Array.

Now try this:

int main() { int vals[ 4 ] = { 1, 2, 3, 4 }; Array a1( 4, vals ); Array a2( a1 ); a1 = a2; return 0; } // Bam!

As before two instances of vals are allocated, and the two then get deleted just fine. So why the crash again?  As before, both instances of Array are pointing to the same instance of vals.  This is due to the default assignment operator that gets called, unless one is explicitly defined.  It only knows how to assign the pointer to vals.  Consequently you also need to explicitly define an assignment operator to complement the copy constructor. Full code listing as follows.

The full picture: copy constructors and assignment operator

#include <algorithm> class Array { private: int size; int* vals; public: ~Array(); Array( int s, int* v ); Array( const Array& a ); Array& operator=( const Array& a ); }; Array::~Array() { delete vals; vals = NULL; } Array::Array( int s, int* v ) { size = s; vals = new int[ size ]; std::copy( v, v + size, vals ); } Array::Array( const Array &a ) { size = a.size; vals = new int[ a.size ]; std::copy( a.vals, a.vals + size, vals ); } Array& Array::operator =(const Array &a) { if ( &a != this ) { size = a.size; vals = new int[ a.size ]; std::copy( a.vals, a.vals + size, vals ); } return *this; } int main() { int vals[ 4 ] = { 1, 2, 3, 4 }; Array a1( 4, vals ); Array a2( a1 ); a1 = a2; return 0; }

Three special kinds of member functions are never inherited:

  1. Copy constructors

  2. Copy assignment operators

  3. Destructors

These three functions are generated automatically by the compiler for classes that do not specify them.

Why Are These Functions Special?

The base class functions are not sufficient to initialize, copy, or destroy a derived instance.

Constructors

For a class that inherits from another, the base class constructor must be called as part of its initialization process. The derived constructor may specify which base class constructor is called in its initialization list.

A class with no constructors is automatically given a compiler-generated, , default constructor that calls the default constructor for each of its base classes. If a class has some constructors but no default constructor, then it has no default initialization. In this case, any derived class constructor must make an explicit base class constructor call in its initialization list.

Order of Initialization

Initialization proceeds in the following order:

  1. Base classes first, in the order in which they are listed in the classHead of the derived class

  2. Data members, in declaration order

Copy Assignment Operators

A copy assignment operator is automatically generated by the compiler for each class that does not have one explicitly defined for it. Because base class data members are generally private, the derived class copy assignment operator must call the base class assignment operator (for each base class) for memberwise copying of those data members to happen. After that, it can perform memberwise assignments of derived class data members.

Other member function operators are inherited the same way as normal member functions.

Copy Constructors

Like the copy assignment operator, a copy constructor is automatically generated for classes that do not have one defined. The compiler-generated copy constructor carries out member-by-member initialization by copying the data members of its argument object.

Example 6.20 defined a class with a single constructor that requires three arguments, so has no default constructor (i.e., the compiler will not generate one). We declare the base class destructor to ensure that the appropriate derived class destructor gets called when it is time to destroy a derived object accessed through a base class pointer.

Example 6.20. src/derivation/assigcopy/account.h

[ . . . . ] class Account { public: Account(unsigned acctNum, double balance, owner); virtual ~Account(){ qDebug() << "Closing Acct - sending e-mail " << "to primary acctholder:" << m_Owner; } virtual getName() const {return m_Owner;} // other virtual functions private: unsigned m_AcctNum; double m_Balance; m_Owner; };

We did not define a copy constructor, which means the compiler will generate one for us. Therefore, this class can be instantiated in exactly two ways: (1) by calling the three-parameter constructor or (2) by invoking the compiler generated copy constructor and supplying an object argument.

Example 6.21 defines a derived class with two constructors. Both of them require base class initialization.

Example 6.21. src/derivation/assigcopy/account.h

[ . . . . ] class JointAccount : public Account { public: JointAccount (unsigned acctNum, double balance, owner, jowner); JointAccount(const Account & acct, jowner); ~JointAccount() { qDebug() << "Closing Joint Acct - sending e-mail " << "to joint acctholder:" << m_JointOwner; } getName() const { return ("%1 and %2").arg(Account::getName()) .arg(m_JointOwner); } // other overrides private: m_JointOwner; };

In Example 6.22, the compiler enables to use for initialization, even though we have not defined it. The compiler-generated copy constructor does memberwise copy/initialization in the order that the data members are listed in the class definition.

Example 6.22. src/derivation/assigcopy/account.cpp

[ . . . . ] Account::Account(unsigned acctNum, double balance, owner) : m_AcctNum(acctNum), m_Balance(balance), m_Owner(owner) { } JointAccount::JointAccount (unsigned acctNum, double balance, owner, jowner) :Account(acctNum, balance, owner), m_JointOwner(jowner) { } JointAccount::JointAccount (const Account& acc, jowner) :Account(acc), m_JointOwner(jowner) { }

Base class initialization required.

Compiler-generated copy constructor call.


Example 6.23 defines a little class that maintains a list of pointers.

Example 6.23. src/derivation/assigcopy/bank.h

[ . . . . ] class Account; class Bank { public: Bank& operator<< (Account* acct); ~Bank(); getAcctListing() const; private: <Account*> m_Accounts; }; [ . . . . ]

This is how to add object pointers to m_Accounts.


in Example 6.24 the construction of the object makes use of the compiler-supplied copy constructor, which calls the compiler-supplied copy constructor.

Example 6.24. src/derivation/assigcopy/bank.cpp

[ . . . . ] #include <> #include "bank.h" #include "account.h" Bank::~Bank() { qDeleteAll(m_Accounts); m_Accounts.clear(); } Bank& Bank::operator<< (Account* acct) { m_Accounts << acct; return *this; } Bank::getAcctListing() const { listing("\n"); foreach(Account* acct, m_Accounts) listing += ("%1\n").arg(acct->getName()); return listing; } int main() { listing; { Bank bnk; Account* a1 = new Account(1, 423, "Gene Kelly"); JointAccount* a2 = new JointAccount(2, 1541, "Fred Astaire", "Ginger Rodgers"); JointAccount* a3 = new JointAccount(*a1, "Leslie Caron"); bnk << a1; bnk << a2; bnk << a3; JointAccount* a4 = new JointAccount(*a3); bnk << a4; listing = bnk.getAcctListing(); } qDebug() << listing; qDebug() << "Now exit program" ; } [ . . . . ]

getName() is virtual.

Begin internal block.

What's this?

At this point, all four Accounts are destroyed as part of the destruction of the bank.


Destructors

Destructors are not inherited. Just as with the copy constructor and copy assignment operator, the compiler generates a destructor if you do not define one explicitly. Base class destructors are automatically called when a derived object is destroyed. Destruction of data members and base class parts occurs in precisely the reverse order of initialization.

6.6.  Constructors, Destructors, and Copy Assignment Operators

Categories: 1

0 Replies to “Destructor Copy Constructor And Assignment Operator Definition”

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *