Keypoints:
- Initialization and cleanup of objects
- Constructor and destructor
- Constructor classification and calling
- When to use copy constructor
- Constructor call rules
- Deep copy and shallow copy
Constructor is a special member function that is executed when an object of that class is created. Destructor is a special member function that is executed when an object of that class is destroyed.
class_name::class_name() {
// body of constructor
}
class_name::~class_name() {
// body of destructor
}
#include <iostream>
using namespace std;
class Person{
public:
Person() {
cout << "Constructor called" << endl;
}
~Person() {
cout << "Destructor called" << endl;
}
};
int main() {
Person p1; // Constructor called automatically when p1 is created
return 0; // p1 is a local variable inside main function, so it is destroyed when main function ends
} // Destructor called automatically when p1 is destroyed/ main function ends
#include <iostream>
using namespace std;
class Person{
public:
int age;
Person() {
cout << "Default constructor called" << endl;
}
Person(int a) {
age = a;
cout << "Parameterized constructor called" << endl;
}
Person(const Person &p) { // const is used to avoid changing the value of p, & is used to avoid copying the whole object
age = p.age; // copy age from p
cout << "Copy constructor called" << endl;
}
~Person() {
cout << "Destructor called" << endl;
}
}
void test01(){
// 1. bracket calling
Person p1; // Default constructor called.
// Don't add () after class name here or it will be treated as a function declaration
Person p2(10); // Parameterized constructor called
Person p3(p2); // Copy constructor called
cout << "p2.age = " << p2.age << endl; // p2.age = 10
cout << "p3.age = " << p3.age << endl; // p3.age = 10
// 2. explicit calling
Person p4; // Default constructor called
Person p5 = Person(10); // Parameterized constructor called
Person p6 = Person(p5); // Copy constructor called
Person(10); // Annymous object, will be destroyed immediately after this line. Add tje `Person p5 = ` before it to keep it explicitly
// Don't use copy constructor to initialize an object
Person (p5); // Wrong, will be treated as a `Person p5;`
// 3. implicit calling
Person p7 = 10; // Parameterized constructor called. Equivalent to `Person p7(10);`
Person p8 = p7; // Copy constructor called. Equivalent to `Person p8(p7);`
}
int main(){
test01();
return 0;
}
When an object is returned from a function by value
#include <iostream>
using namespace std;
class Person{
public:
Person() {
cout << "Default constructor called" << endl;
}
Person(int a) {
age = a;
cout << "Parameterized constructor called" << endl;
}
Person(const Person &p) {
age = p.age;
cout << "Copy constructor called" << endl;
}
~Person() {
cout << "Destructor called" << endl;
}
int age;
}
// 1. When an object is initialized by another object of the same class
void test01(){
Person p1(10); // Parameterized constructor called
Person p2(p1); // Copy constructor called
cout << "p2.age = " << p2.age << endl; // p2.age = 10
}
// 2. When an object is passed by value as a parameter to a function
void doWork(Person p) { // p is a local variable inside this function, it copies the value of the object passed in, equivalent to `Person p = p1;`
cout << "p.age = " << p.age << endl; // p.age = 10
}
void test02(){
Person p1(10); // Parameterized constructor called
doWork(p1); // Copy constructor called
}
// 3. When an object is returned from a function by value
Person doWork2() {
Person p1(10);
cout << "p1.age = " << p1.age << endl; // p1.age = 10
return p1; // Copy constructor called
}
void test03(){
Person p2 = doWork2(); // Copy constructor called, equivalent to `Person p2 = p1;` Two copy constructors are called in total
cout << "p2.age = " << p2.age << endl; // p2.age = 10
}
int main(){
test01();
test02();
test03();
return 0;
}
If we specify a copy constructor, the compiler will not generate a default constructor or a parameterized constructor.
#include <iostream>
using namespace std;
class Person{
public:
Person() {
cout << "Default constructor called" << endl;
}
// if we specify a parameterized constructor, the compiler will not generate a default constructor. Then `Person p1;` will be wrong
Person(int a) {
age = a;
cout << "Parameterized constructor called" << endl;
}
~Person() {
cout << "Destructor called" << endl;
}
int age;
}
void test01(){
Person p1; // Default constructor called
p1.age = 10;
Person p2(p1); // No output, because the copy constructor is generated by the compiler automatically
cout << "p2.age = " << p2.age << endl; // p2.age = 10
}
If there is a heap memory in constructor, customized copy constructor needed and deep copy are necessary for avoiding releasing heap memory repeatedly.
#include <iostream>
using namespace std;
class Person{
public:
Person() {
cout << "Default constructor called" << endl;
}
Person(int a, int h) {
age = a;
height = new int(h); // allocate heap memory for height
cout << "Parameterized constructor called" << endl;
}
Person(const Person &p){
cout << "Copy constructor called" << endl;
age = p.age;
height = new int(*p.height) // a different pointer but refers to the same address
} // Deep copy will allocate a new pointer referring to the same address, so the repeated release is solved.
~Person() {
// release the heap memory
if (height != NULL) {
delete height;
height = NULL; // avoid dangling pointer
}
cout << "Destructor called" << endl;
}
int age;
int *height;
}
void test01(){
Person p1(18, 160);
cout << "p1.age = " << p1.age << endl; // p1.age = 18
cout << "p1.height = " << *p1.height << endl; // p1.height = 160
Person p2(p1); // Shallow copy, only copy the value of the pointer
cout << "p2.age = " << p2.age << endl; // p2.age = 18
cout << "p2.height = " << *p2.height << endl; // p2.height = 160
}
// wrong, the heap memory will be released repeatedly in deconstructor
int main(){
test01();
return 0;
}