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++
- Make base class copy constructor ‘protected’ so that only derived classes will have access privilege to base class copy constructors.
- Make derived class copy constructor ‘private’ so that it is not accessible outside the class.
- 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:
- Make use of unique pointers instead of raw pointers thus avoiding any issues related to ownership and life cyle management of objects.
- 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.
- 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.
- Cannot use C++ copy constructor directly for derived class object copy
- Define virtual clone() method in Base and override that in Derived class
- These clone() methods uses the protected/private copy constructors for creating clones.
- clone() gives away the ownership of the created object as clone() is just a factory method here.
- Client code has to clean-up the cloned objects after their use in the code or before returning leading to naked ‘delete’.
- To resolve issue in step ‘5’ make use of C++ std::unique_ptr
- To create std::unique_ptr the C++ 14 way, we have to use std::make_unique.
- Make the outsider function, std::make_unique a friend of Base and Derived class.
- 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
- Move construction
- Copy assignment
- 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:
- 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.
- 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.
- 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! 🙂