Chapter 7 : The Philosophy Of C++, Part C

Key words: copy derived class objects, cloning in C++, moving derived class objects

Topics at a glance:

  • Introduction to cloning idiom in C++
  • Derived class objects and the copy conundrum
  • How to clone correctly ?
  • Moving objects of derived classes

Derived Class Objects : The Copy Conundrum!

When an object of a derived class has to be copied, then we cannot go with the usual approach of copy constructors. Because what if the client code who is going to initiate the copy operation is adhering to our code to base class object principle. In that case, we cannot resort to virtual constructors.

Because in C++ there is no virtual constructor, unlike virtual destructors. So in that case if the actual object to clone from is a type of derived class, then client code will end up calling just the base class constructor and not the derived class constructor because of lack of polymorphic behavior for constructors in C++. You get the issue here? What you asked is to clone from a derived class instance, and what you got is just a copy of a base class instance. i.e. object is only partly copied. This problem is known as ‘object slicing‘ in C++. So the question now is how to avoid this object slicing’ from happening?

Copy derived class objects

Cloning in C++

  1. Make base class copy constructor ‘protected’ so that only derived classes will have access privilege to base class copy constructors.
  2. Make derived class copy constructor ‘private’ so that it is not accessible outside the class.
  3. Define a virtual clone() member function for your base class and override this in derive class.

Now, let us see the above steps in action. I have written a code for cloning a derived class object. The client code here is the main() function itself, and sticks to the code to a base class object  principle.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#include <iostream>

using namespace std;

class Base
{
private:
    int a_;
public:
    Base(const int a = 0) : a_{a}
    {
        cout << "Base created with a_ : " << a_ << endl;
    }
    
    virtual ~Base()
    {
        cout << "Base object destroyed" << endl;
    }
protected: // MUST be protected so that this wont be available outside base/child classes 
    // copy constructor 
    Base(const Base& other_object)
    {
        a_ = other_object.a_;
        cout << "Base created with a_ : " << a_ << endl;
    }
public:
    virtual Base* clone() 
    {
        cout << "Cloning Base object..." << endl;
        return new Base(*this); // invokes Base copy constructor 
    }
    
    virtual void print()
    {
        cout << "a_ : " << a_ << endl; 
    }
};

class Derived : public Base
{
private:
    int b_;
    int c_;
public:
    Derived(const int a = 0, const int b = 0, const int c = 0): Base(a), b_{b}, c_{c}
    {
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
    ~Derived()
    {
        cout << "Derived object destroyed" << endl;
    }
private:
    // copy constructor is made private 
    Derived(const Derived& other_object) : Base(other_object)
    {
        b_ = other_object.b_;
        c_ = other_object.c_;
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
public:
    Base* clone() override
    {
        cout << "cloning Derived object..." << endl;
        return new Derived(*this);
    }
    
    void print() override
    {
        // First print Base 
        Base::print();
        cout << "b_ : " << b_ << "\nc_ : " << c_ << endl;
    }
    
};

int main()
{
    Derived d1(1, 2, 3);
    
    Base *b1 = d1.clone();
    
    b1->print();    
    
    cout << "\nCreating a second clone of Derived..." << endl;
    Base *b2 = b1->clone();
    
    b2->print();
    
    // Destroy all cloned objects 
    cout << "\nDestroying cloned objects..." << endl;
    
    delete b1;
    delete b2;
    
    cout << "Cloned objects destroyed" << endl;
    
    cout << "\nAt the end of main() Object 'd1' in main's stack will also be destroyed!" << endl;
    
    return 0;
}

Now, let us see the result of this code when executed:

Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
Creating a second clone of Derived...
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
Destroying cloned objects...
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Cloned objects destroyed
At the end of main() Object 'd1' in main's stack will also be destroyed!
Derived object destroyed
Base object destroyed

It works fine, but with a hidden major issue with the implementation. The issue is, client code unknowingly takes up the ownership of the cloned objects. The clone objects are actually being constructed in the free store by the clone() function and the function give away the ownership to client. So it is client responsibility to release the memory properly after using it. In this case, I know that main() is going to end and exit. But in real programs client code won’t be a main and it won’t exit the program right after cloning and using the objects and most likely client code returns without cleaning up the cloned objects and memory is never released back to free store. The issue will become worse if the objects have some other resources that are acquired inside, like files, sockets etc. So what would have happened if cloned objects are not destroyed properly? To avoid such issues, client code has to delete the cloned objects and clean the mess up just before returning. But as I have said in the session above, the principle of RAII just asks the programmers to restrain from using naked new and delete. In this case, ‘new’ is not naked as it is hidden inside the clone() method, but since the clone() has given away the ownership of the object, now its client’s responsibility to directly call delete; and moreover a naked delete, violating RAII.

So, to avoid this, make use of modern C++ ‘s smart pointers. Let us see the steps below:

  1. Make use of unique pointers instead of raw pointers thus avoiding any issues related to ownership and life cyle management of objects.
  2. In the code here, Base class’s copy constructor is protected and Derived class’s copy constructor is private. From C++ 14 onward, std::unique_ptr has to be constructed using a factory method ‘std::make_unique’. This method is an outsider for the Base and Derived class and therefore it’s not possible to manipulate creation of Base/Derived objects via the private/protected copy constructors.
  3. To circumvent the issue mentioned in step 2, one solution is to use the C++’s powerful friend function. (I agree long-distance friendships are not recommended, but for now let’s go with it)

So, with all these in our armoury let us resolve this issue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <iostream>
#include <memory>

using namespace std;

class Base
{
private:
    int a_;
public:
    Base(const int a = 0) : a_{a}
    {
        cout << "Base created with a_ : " << a_ << endl;
    }
    
    virtual ~Base()
    {
        cout << "Base object destroyed" << endl;
    }
protected: // MUST be protected so that this wont be available outside base/child classes 
    // copy constructor 
    Base(const Base& other_object)
    {
        a_ = other_object.a_;
        cout << "Base created with a_ : " << a_ << endl;
    }
public:
    virtual unique_ptr<Base> clone() 
    {
        cout << "Cloning Base object..." << endl;
        return make_unique<Base>(*this);
    }
    
    virtual void print()
    {
        cout << "a_ : " << a_ << endl; 
    }
    
    friend unique_ptr<Base> make_unique<Base>(Base&);
};

class Derived : public Base
{
private:
    int b_;
    int c_;
public:
    Derived(const int a = 0, const int b = 0, const int c = 0): Base(a), b_{b}, c_{c}
    {
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
    ~Derived()
    {
        cout << "Derived object destroyed" << endl;
    }
private:
    // copy constructor is made private 
    Derived(const Derived& other_object) : Base(other_object)
    {
        b_ = other_object.b_;
        c_ = other_object.c_;
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
public:
    unique_ptr<Base> clone() override
    {
        cout << "cloning Derived object..." << endl;
        return make_unique<Derived>(*this);
    }
    
    void print() override
    {
        // First print Base 
        Base::print();
        cout << "b_ : " << b_ << "\nc_ : " << c_ << endl;
    }
    
    friend unique_ptr<Derived> make_unique<Derived>(Derived&);
};

int main()
{
    Derived d1(1, 2, 3);
    
    unique_ptr<Base> b1 = d1.clone();
    
    b1->print();    
    
    cout << "\nCreating a second clone of Derived..." << endl;
    unique_ptr<Base> b2 = b1->clone();
    
    b2->print();
    
    cout << "\nAt the end of main() Object 'd1' in main's stack,"
         << "\nalong with cloned objects will be destroyed!" << endl;
    
    return 0;
}

The main thing to note here is that in the client code i.e. main(), there is no need to call delete anymore. ‘unique_ptr’ takes care of this automatically. Now, let’s see the result.

Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
Creating a second clone of Derived...
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
At the end of main() Object 'd1' in main's stack,
along with cloned objects will be destroyed!
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed

Let us summarize the things we’ve done for making a copy of derived class object.

  1. Cannot use C++ copy constructor directly for derived class object copy
  2. Define virtual clone() method in Base and override that in Derived class
  3. These clone() methods uses the protected/private copy constructors for creating clones.
  4. clone() gives away the ownership of the created object as clone() is just a factory method here.
  5. Client code has to clean-up the cloned objects after their use in the code or before returning leading to naked ‘delete’.
  6. To resolve issue in step ‘5’ make use of C++ std::unique_ptr
  7. To create std::unique_ptr the C++ 14 way, we have to use std::make_unique.
  8. Make the outsider function, std::make_unique a friend of Base and Derived class.
  9. Voila! 😊

Moving derived class objects

Now, as an extension of the clone idiom, let us develop the rest of the copy/move operations such as

  1. Move construction
  2. Copy assignment
  3. Move assignment

Move constructor:

Do you remember that the main reason for clone() method for copy construction was that there is no virtual constructor in C++. The same is applicable for move construction as well. So, for move construction also, we have to come up with a polymorphic method similar to clone. Here, I am defining polymorphic move() methods for Base and Derived classes just like their clone() counterparts.

i.e. For Base class move will be virtual, which will get overridden by Derived class’s move. Rest of the things are similar to the behavior of regular move construction operation.

One important thing to note in move construction is that we have to declare std::make_unique as a friend function of both Base and Derived class to take r-value references of Base and Derived. i.e.

‘friend unique_ptr<Base> std::make_unique<Base>(Base&&);’

‘friend unique_ptr< Derived > std::make_unique< Derived >(Derived&&);’

Copy assignment:

Unlike its constructor counterpart, here we can make use of assignment ‘operator=’ for Base-Derived class hierarchy.

There is no need of an explicit polymorphic method for assignment operations. Just define the usual copy assignment variant of ‘operator=’ for both base and derived classes. But make sure you call the base variant of “operator=’ from derived’s ‘operator=’ and this must be the first thing to do i.e to copy base class object data first before derived class object data. This call chaining of ‘operator=’, in an ordered manner, is required as you cannot define virtual copy/move assignments as class names will not match between base and derived, thus making the function signatures differ between base and derived variants of ‘operator=’.

Move assignment:

Unlike its constructor counterpart in move assignment also, we don’t need another polymorphic method. Define the move assignment variant of ‘operator=’ for both base and derived, and do a similar call chaining I have mentioned in copy assignment above. i.e. to call base’s ‘operator=’ variant before derived’s variant.

The important things to note for copy/move ‘operator=’ are:

  1. You cannot make virtual copy/move assignment operators as the function signatures must match. For base class it uses ‘const Base&‘ or ‘Base&&‘ for copy and move assignment ‘operator=’ respectively, whereas for derived class it uses ‘const Derived&‘ or ‘Derived&&‘ for copy and move ‘operator=’ respectively. For polymorphic behavior function signatures must match exactly.
  2. You have to call-chain. i.e derived’s variant of ‘operator=’ must invoke the base’s variant and only then both base and derived object data will get copied/moved.
  3. The order of call-chain is so important. i.e base variant of ‘operator=’ must be called first to copy/move base object’s data before derived’s. Because in some classes, derived will inherit base’s protected and public data members and may even overwrite these values. So if base class copy/move is done after derived, then it’s likely to overwrite the derived object’s data with base object data.

Let us put this information into action now:

The below code implements copy assignment, move constructor and move assignment operation of Base-Derived class system along with the copy construction’s clone as per the above mentioned ways.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#include <iostream>
#include <memory>

using namespace std;

class Base
{
private:
    int a_;
public:
    Base(const int a = 0) : a_{a}
    {
        cout << "Base created with a_ : " << a_ << endl;
    }
    
    virtual ~Base()
    {
        cout << "Base object destroyed" << endl;
    }
protected: // MUST be protected so that this wont be available outside base/child classes 
    // copy constructor 
    Base(const Base& other_object)
    {
        a_ = other_object.a_;
        cout << "Base created with a_ : " << a_ << endl;
    }
    // Base class move constructor
    Base(Base&& other_object)
    {
        a_ = other_object.a_;
        other_object.a_ = 0;
        cout << "Base object has been move-constructed with a_ : " << a_ << endl;
    }
public:
    // copy assignment
    Base& operator=(const Base& other_object)
    {
        a_ = other_object.a_;
        cout << "Base object has been copy assigned with a_ : " << a_ << endl;
        return (*this);
    }
    
    // move assignment
    Base& operator=(Base&& other_object)
    {
        a_ = other_object.a_;
        other_object.a_ = 0;
        
        cout << "Base object has been move assigned with a_ : " << a_ << endl;
        
        return (*this);
    }
    
    virtual unique_ptr<Base> clone() 
    {
        cout << "Cloning Base object..." << endl;
        return make_unique<Base>(*this);
    }
    
    virtual unique_ptr<Base> move()
    {
        cout << "Moving Base object..." << endl;
        return make_unique<Base>(std::move(*this));
    }
    
    virtual void print()
    {
        cout << "a_ : " << a_ << endl; 
    }
    
    friend unique_ptr<Base> make_unique<Base>(Base&);
    friend unique_ptr<Base> make_unique<Base>(Base&&);
};

class Derived : public Base
{
private:
    int b_;
    int c_;
public:
    Derived(const int a = 0, const int b = 0, const int c = 0): Base(a), b_{b}, c_{c}
    {
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
    ~Derived()
    {
        cout << "Derived object destroyed" << endl;
    }
private:
    // copy constructor is made private 
    Derived(const Derived& other_object) : Base(other_object)
    {
        b_ = other_object.b_;
        c_ = other_object.c_;
        cout << "Derived created with b_ : " << b_ << " c_ : " << c_ << endl;
    }
    // move constructor
    Derived(Derived&& other_object): Base(std::move(other_object)) 
    {
        b_ = other_object.b_;
        c_ = other_object.c_;
        
        other_object.b_ = 0;
        other_object.c_ = 0;
        
        cout << "Derived object has been move-constructed with b_ : " << b_ 
             << " c_ : " << c_ << endl;
    }
public:
    // copy assignment
    Derived& operator=(const Derived& other_object)
    {
        // first assign base class object
        Base::operator=(static_cast<const Base&>(other_object)); 
        // no need to take the returned value as it is the same 'this' object
        
        b_ = other_object.b_;
        c_ = other_object.c_;
        cout << "Derived object has been copy assigned with b_ : " << b_ 
             << " c_ : " << c_ << endl;
             
        return (*this);
    }
    
    // move assignment
    Derived& operator=(Derived&& other_object)
    {
        // first assign base class object
        Base::operator=(static_cast<Base&&>(std::move(other_object))); 
        // no need to take the returned value as it is the same 'this' object
        
        b_ = other_object.b_;
        c_ = other_object.c_;
        
        other_object.b_ = 0;
        other_object.c_ = 0;
        
        cout << "Derived object has been move assigned with b_ : " << b_ 
             << " c_ : " << c_ << endl;
             
        return (*this);
    }
    unique_ptr<Base> clone() override
    {
        cout << "cloning Derived object..." << endl;
        return make_unique<Derived>(*this);
    }
    
    unique_ptr<Base> move() override
    {
        cout << "Moving Derived object..." << endl;
        return make_unique<Derived>(std::move(*this));
    }
    
    void print() override
    {
        // First print Base 
        Base::print();
        cout << "b_ : " << b_ << "\nc_ : " << c_ << endl;
    }
    
    friend unique_ptr<Derived> make_unique<Derived>(Derived&);
    friend unique_ptr<Derived> make_unique<Derived>(Derived&&);
};

void test_move_assignment()
{
    cout << "\nTesting move assignment..." << endl;
    
    Derived d5(10, 11, 12);
    
    Derived d6;
    
    d6 = std::move(d5);
    
    d6.print();
    
    cout << "Testing move assignment done\n" << endl;
    
    return;    
}

void test_copy_assignment()
{
    cout << "\nTesting copy assignment..." << endl;
    
    Derived d3(7, 8, 9);
    
    Derived d4;
    
    d4 = d3;
    
    d4.print();
    
    cout << "Testing copy assignment done\n" << endl;
    
    return;
}

void test_move_semantics()
{
    cout << "\nTesting move semantics..." << endl;
    
    Derived d2(4, 5, 6);
    
    unique_ptr<Base> b3 = d2.move();
    
    b3->print();
    
    cout << "Testing move semantics done!\n" << endl;
    
    return;
}

void test_clone()
{
    Derived d1(1, 2, 3);
    
    unique_ptr<Base> b1 = d1.clone();
    
    b1->print();    
    
    cout << "\nCreating a second clone of Derived..." << endl;
    unique_ptr<Base> b2 = b1->clone();
    
    b2->print();
    
    cout << "\nAt the end of test_clone() Object 'd1' in test_clone's stack,"
         << "\nalong with cloned objects will be destroyed!" << endl;
         
    return;
    
}

int main()
{
    test_clone();
    
    test_move_semantics();
    
    test_copy_assignment();
    
    test_move_assignment();    
    
    return 0;
}

Now, let us see the results:

Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
Creating a second clone of Derived...
cloning Derived object...
Base created with a_ : 1
Derived created with b_ : 2 c_ : 3
a_ : 1
b_ : 2
c_ : 3
At the end of test_clone() Object 'd1' in test_clone's stack,
along with cloned objects will be destroyed!
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Testing move semantics...
Base created with a_ : 4
Derived created with b_ : 5 c_ : 6
Moving Derived object...
Base object has been move-constructed with a_ : 4
Derived object has been move-constructed with b_ : 5 c_ : 6
a_ : 4
b_ : 5
c_ : 6
Testing move semantics done!
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Testing copy assignment...
Base created with a_ : 7
Derived created with b_ : 8 c_ : 9
Base created with a_ : 0
Derived created with b_ : 0 c_ : 0
Base object has been copy assigned with a_ : 7
Derived object has been copy assigned with b_ : 8 c_ : 9
a_ : 7
b_ : 8
c_ : 9
Testing copy assignment done
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed
Testing move assignment...
Base created with a_ : 10
Derived created with b_ : 11 c_ : 12
Base created with a_ : 0
Derived created with b_ : 0 c_ : 0
Base object has been copy assigned with a_ : 10
Derived object has been move assigned with b_ : 11 c_ : 12
a_ : 10
b_ : 11
c_ : 12
Testing move assignment done
Derived object destroyed
Base object destroyed
Derived object destroyed
Base object destroyed.

I hope you have understood the copy and move semantics to be applied for a Base-Derived class hierarchy now.

Enjoyed the chapter? Let me in the comments below. Thanks! 🙂

0

Leave a Reply