This post discuss all topics about constructors.
Concept: Rule of three and rule of five
- The rules basically advise that you should explicitely defines default constructor, copy constructor, move constructor, assignment operator overload, move operator, and destructor.
- Sample implementation rule of five
class person
{
std::string name;
int age;
public:
person(const std::string& name, int age); // Ctor
person(const person &) = default; // 1/5: Copy Ctor
person(person &&) noexcept = default; // 4/5: Move Ctor
person& operator=(const person &) = default; // 2/5: Copy Assignment
person& operator=(person &&) noexcept = default; // 5/5: Move Assignment
~person() noexcept = default; // 3/5: Dtor
};
Implementation
Implementing copy constructor
- why do we need? the object is used a Left-Value such as the following cases
- When an object of the class is returned by value.
- When an object of the class is passed (to a function) by value as an argument.
- When an object is constructed based on another object of the same class.
- When the compiler generates a temporary object.
// return a car from inventory in Feb
// the obj is returned by value, (it is left-value)
Car newCar = FactoryStore.collectInventory()
// the object is passed to function as an argument, and a copy of Car is made
vector<Car> carCollection;
carCollection.push_back(newCar);
- what do we consider? shallow copy or deep copy
The following program illustrates that a copy of MyClass
will not have independent dynamic memory allocated if deep copy is not implemented.
class MyClass {
int getA() { return a; };
void setA(int x) { a = x; };
int getB() { return b; };
void setB(int x) { *b = x; };
int a =0;
int * b;
};
int main(){
MyClass class1;
class1.setA(5);
class2.setB(10);
MyClass class2 = a;
std::cout<<class2.getA()<<std::endl; // will print out 5 because the value is copied
std::cout<<class2.getB()<<std::endl; // will print out 10 because the pointer class2->b points to the same memory location as class1->b
class2.setB(20);
std::cout<<class2.getB()<<std::endl; // will print out 20 because the pointer class2->b points to the same memory location as class1->b
}
- The following illustrates how to implement deep copy with copy constructor. It also shows the set back with copy constructor.
#include <iostream>
#include <iomanip>
#include <vector>
class MyClass {
private:
static int count_;
int id_{ count_++ };
void log(char const *s) const {
std::cout << '[' << this << "] " << std::left << std::setw(30) << s;
std::cout << std::right << std::setw((id_ + 1) * 3) << '(' << id_ << ")\n";
}
void log(char const *s, int from) const {
std::string str{ s };
str += " (from ";
str += std::to_string(from);
str += ")";
log(str.c_str());
}
public:
MyClass() { log("Default constructor"); }
MyClass(MyClass const& s) { log("Copy constructor", s.id_); }
// MyClass(MyClass&& s) noexcept { log("Move constructor", s.id_); }
auto operator= (MyClass const& s) -> MyClass& { log("Copy Assignment", s.id_); return *this; }
auto operator= (MyClass&& s) noexcept -> MyClass& { log("Move assignment", s.id_); return *this; }
~MyClass() { log("Destructor"); }
};
int MyClass::count_ = 0;
int main()
{
std::vector<MyClass> vec;
for (int i = 0; i < 4; i++) {
std::cout << "index: " << i << " capacity: " << vec.capacity() << '\n';
MyClass obj;
vec.push_back(obj);
}
std::cout << "main loop ends\n";
return 0;
}
index: 0 capacity: 0
[0x7ffeb80fcf58] Default constructor (0)
[0x55d9f1d4f2c0] Copy constructor (from 0) (1)
[0x7ffeb80fcf58] Destructor (0)
index: 1 capacity: 1
[0x7ffeb80fcf58] Default constructor (2)
[0x55d9f1d4f2e4] Copy constructor (from 2) (3)
[0x55d9f1d4f2e0] Copy constructor (from 1) (4)
[0x55d9f1d4f2c0] Destructor (1)
[0x7ffeb80fcf58] Destructor (2)
index: 2 capacity: 2
[0x7ffeb80fcf58] Default constructor (5)
[0x55d9f1d4f2c8] Copy constructor (from 5) (6)
[0x55d9f1d4f2c0] Copy constructor (from 4) (7)
[0x55d9f1d4f2c4] Copy constructor (from 3) (8)
[0x55d9f1d4f2e0] Destructor (4)
[0x55d9f1d4f2e4] Destructor (3)
[0x7ffeb80fcf58] Destructor (5)
index: 3 capacity: 4
[0x7ffeb80fcf58] Default constructor (9)
[0x55d9f1d4f2cc] Copy constructor (from 9) (10)
[0x7ffeb80fcf58] Destructor (9)
main loop ends
[0x55d9f1d4f2c0] Destructor (7)
[0x55d9f1d4f2c4] Destructor (8)
[0x55d9f1d4f2c8] Destructor (6)
[0x55d9f1d4f2cc] Destructor (10)
Implementing move constructor
- The following illustrates basic implementation of move constructor
class MyClass {
// Move constructor other to new location
// double && makes r-value reference
MyClass (const MyClass && other){
a = other.a;
b = other.b; // point to the temporary obj
other.b = nullptr; // null out the temporary obj
}
int a =0;
int * b;
};
- Why could move constructor be more useful than copy constructor?
- Copy constructor works with Left Values while Move Contructor works with Right Value references
- move constructor makes the member pointer to point to the already temporary existing object in the heap.
- then it nulls out the pointer of the temporary existing object.
- move constructor is only invoked when the input is right-value. Therefore:
// implement the move constructor with double &&
MyClass (const MyClass && other)
// call the move constructor by passing the class directly to function call:
vec.push_back(MyClass());
- The following shows how changes are made after move constructor is added
#include <iostream>
#include <iomanip>
#include <vector>
class MyClass {
private:
static int count_;
int id_{ count_++ };
void log(char const *s) const {
std::cout << '[' << this << "] " << std::left << std::setw(30) << s;
std::cout << std::right << std::setw((id_ + 1) * 3) << '(' << id_ << ")\n";
}
void log(char const *s, int from) const {
std::string str{ s };
str += " (from ";
str += std::to_string(from);
str += ")";
log(str.c_str());
}
public:
MyClass() { log("Default constructor"); }
MyClass(MyClass const& s) { log("Copy constructor", s.id_); }
MyClass(MyClass&& s) noexcept { log("Move constructor", s.id_); }
auto operator= (MyClass const& s) -> MyClass& { log("Copy Assignment", s.id_); return *this; }
auto operator= (MyClass&& s) noexcept -> MyClass& { log("Move assignment", s.id_); return *this; }
~MyClass() { log("Destructor"); }
};
int MyClass::count_ = 0;
int main()
{
std::vector<MyClass> vec;
for (int i = 0; i < 4; i++) {
std::cout << "index: " << i << " capacity: " << vec.capacity() << '\n';
// MyClass obj;
vec.push_back(MyClass()); // note that passing MyClass obj will not trigger move constructor
}
std::cout << "main loop ends\n";
return 0;
}
- We have a significant decrease in the number of constructors being called
index: 0 capacity: 0
[0x7ffcc4051268] Default constructor (0)
[0x556028cfb2c0] Move constructor (from 0) (1)
[0x7ffcc4051268] Destructor (0)
index: 1 capacity: 1
[0x7ffcc4051268] Default constructor (2)
[0x556028cfb2e4] Move constructor (from 2) (3)
[0x556028cfb2e0] Move constructor (from 1) (4)
[0x556028cfb2c0] Destructor (1)
[0x7ffcc4051268] Destructor (2)
index: 2 capacity: 2
[0x7ffcc4051268] Default constructor (5)
[0x556028cfb2c8] Move constructor (from 5) (6)
[0x556028cfb2c0] Move constructor (from 4) (7)
[0x556028cfb2e0] Destructor (4)
[0x556028cfb2c4] Move constructor (from 3) (8)
[0x556028cfb2e4] Destructor (3)
[0x7ffcc4051268] Destructor (5)
index: 3 capacity: 4
[0x7ffcc4051268] Default constructor (9)
[0x556028cfb2cc] Move constructor (from 9) (10)
[0x7ffcc4051268] Destructor (9)
main loop ends
[0x556028cfb2c0] Destructor (7)
[0x556028cfb2c4] Destructor (8)
[0x556028cfb2c8] Destructor (6)
[0x556028cfb2cc] Destructor (10)
References:
Move constructors in C with examples
Best Move constructor implementation practices.
Understanding the meaning of left and right values.
Also credit to Bob_ who help me with the example on my stackoverflow question.
Implement assignment operator
- The following illustrates how to implement assignment operator
MyClass& operator=(const MyClass & other) { // deep copy assignment
a = other.a;
other->b = new int(); // allocate new memory location for the pointer
other->b = *b; // copy value
}
- when would assignment operator be useful?
Concept: Singleton
The topic of singleton is discussed in details in another post: Different ways to implement Singleton in C++ β My sky (freewindcode.com)