[C + +] classes and objects (middle) -- constructor + destructor + copy constructor

Posted by imartin on Wed, 19 Jan 2022 20:01:11 +0100

1. The default six member functions of the class

If a class has no members, it is called an empty class. Is there nothing in the empty class? Not really. If we don't write any class, the following six default member functions will be automatically generated. This is the complex initialization mechanism of C + +.

class Date{}

They are special member functions with many special points, which will be expanded one by one later.

🍬 Xiao Bian has something to say: for the default member function introduced below. We should write according to the rules and know what to write. If we don't write, the compiler will generate a copy by itself. What are their characteristics? It's also a complex place here. We should also know well, or decide whether I write it or not.

2. Constructor

2.1 constructor concept

Constructors are special member functions. Note that although the name of the constructor is called construction, the main task of the constructor is not to open space to create objects, but to initialize objects.

When I wrote the data structure, I suffered such a loss. Finally, I made a strange error. After debugging, I found that I forgot to call the initialization function. I don't seem to have an intuitive feeling about forgetting to destroy it, but I've also seen stories of others forgetting to release resources and hanging up the server.

🍓 Then the constructor is called automatically after the object is defined to ensure that the object must be initialized.

2.2 constructor features

🍓 Characteristics——

  1. The function name is the same as the class name
  2. No return value
  3. When an object is instantiated, the compiler automatically calls the corresponding constructor
  4. Constructors can be overloaded

❄️ Look at the date class——

class Date
{
public:
	//1. Parameterless constructor
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 1;
	}
	//2. Constructor with parameters - initialize to the specified value
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//Automatically called when an object is instantiated
	Date d1;//Call parameterless constructor
	Date d2(2022, 1, 17);
	return 0;
}

The above two constructors constitute function overloading. In fact, they can also be combined into one function to achieve the same function - that is, through the full default [recommended] like this

	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

Note: parameterless constructor Date(); And full default function Date(int year = 2002, int month = 2, int day = 19); It constitutes a function overload, which can exist at the same time in syntax. However, if there is no parameter, call Date d1;, There is ambiguity and an error will be reported.

❄️ 5. However, if we do not write a constructor in the class, the C + + compiler will automatically generate a parameterless default constructor (once the user explicitly defines it, the compiler will no longer generate it). The d1 object calls the compiler generated default constructor, but the d object_ year /_month/_day is still a random value. So what does the default generated constructor do?

In C + +, types are divided into two categories——

  • Built in type (basic type) -- C language native array with type int/char/double / pointer / built-in type
  • Custom type - type defined by struct/class

🍓 We don't write anything, and the compiler generates constructors by default——

  • Member variables of built-in types are not processed

  • For a member variable of a custom type, it will be initialized by calling its default constructor (that is, it can be called without passing parameters)

    Note: if there is no constructor, the compiler will report an error. (for example, I explicitly wrote a Date() with parameters)

To this end, a custom type is written to verify the second point - for a custom type, its default constructor will be called

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
private:
	int _a;
};

class Date
{
public:
	
private:
	int _year;
	int _month;
	int _day;

	A _aa;
};

int main()
{
	//Automatically called when an object is instantiated
	Date d1;
	return 0;
}

As you can see, it's really adjusted_ aa's default constructor, printed——

Let's explain the bet -- the compiler will report an error if there is no constructor

If I modify class A in the previous code, I will report an error——

When we instantiate d1 in the above code, we don't write anything in the Date class. For custom type variables_ aa, will call its parameterless default constructor. For class A, we didn't write a parameterless / full default constructor, and then deliberately wrote a parameterless constructor, so the compiler didn't generate it again. There is no default constructor adjustable, so an error is reported.

❄️ 6. There are three default constructors for any class (which can be called without parameters) - parameterless constructors, full default constructors, and constructors generated by the compiler by default. Both parameterless constructors and fully default constructors are called default constructors, and there can only be one default constructor (syntactically, they can exist at the same time, but if there is an object definition to call, an error will be reported).

3. Destructor

3.1 destructor concept

Contrary to the constructor function, the destructor does not complete the destruction of objects, and the destruction of local objects is completed by the compiler. When the object is destroyed, it will automatically call the destructor to clean up some resources of the object.

3.2 characteristics of destructors

Destructors are special member functions.

🍓 Characteristics——

  1. The destructor name is preceded by the character ~.
  2. No parameter, no return value.
  3. A class has and has only one destructor (no arguments can constitute an overload). If not explicitly defined, the system automatically generates a default destructor.
  4. At the end of the object life cycle, the C + + compilation system automatically calls the destructor.

Take the Date class as an example. Through printing / debugging, you can see that the compiler automatically calls the destructor at the end of the d1 life cycle——

class Date
{
public:
    Date(int year = 2002, int month = 2, int day = 19)
	{
		_year = year;
		_month = month;
		_day = day;
	}
     
	~Date()
	{
		cout << "~Date()" << endl;
	}
    
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	return 0;
}

Debugging found that the destructor didn't seem to do anything. In fact, this Date class has no resources to clean up, and not all classes need destructors. Therefore, it is possible for it not to implement a destructor.

For the stack class we implemented before——

class Stack
{
public:
	//Constructor
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr)
		{
			cout << "malloc failed" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	//Destructor
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(20);
	return 0;
}

This ensures that the stack must be initialized when it is defined; Out of scope, the space requested on the heap must be recycled. You won't forget to manually Init and Destroy.

Note: Deconstruction sequence? st2 is cleaned first, and st1 is cleaned later (for debugging, see)

  1. If we don't write the destructor that the compiler automatically generates, what will we do?

🍓 Similar to constructors, it——

  • Member variables of built-in types are not processed
  • For a member of a custom type, the variable will go back to its destructor

Let's use two stacks to realize the queue as an example (I won't talk about the topic idea here, but the idea depends on my topic solution), mainly focusing on the role of default generation——

class Stack
{
public:
	//Constructor
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr)
		{
			cout << "malloc failed" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	//Destructor
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue {
public:
	// We don't need to write constructors and destructors
    // The default generated is very useful
	// For a custom type, its default constructor and destructor are automatically called
	/*MyQueue() {} */
    
	void push(int x) {}
	int pop() {}
	int peek() {}
	bool empty() {}
    
private:
	Stack _pushST;
	Stack _popST;
};

int main()
{
	MyQueue mq;
	return 0;
}

The previous C language implementation, hey, needs to be called manually——

MyQueue* myQueueCreate() {
    MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&q->pushST);
    StackInit(&q->popST);
    return q;
}

void myQueueFree(MyQueue* obj) {
    StackDestroy(&obj->pushST);
    StackDestroy(&obj->popST);
    free(obj);
}

4. Copy constructor

4.1 copy constructor features

The copy constructor is also a special member function.

🍓 Its characteristics are as follows:

  1. The copy constructor is an overloaded form of the constructor. Its function name is the class name and has no return value.

  2. There is only one parameter of the copy constructor, and the parameter must be passed by reference. Using the value passing method will cause infinite recursive calls.

class Date
{
public:
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	//Constructor
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2002, 3, 7);
	Date d2(d1);
	return 0;
}

Note: const date is often quoted here. The most obvious reason is to prevent MIS writing. Of course, there are many reasons for follow-up learning.

🍓 Let's explain why copying constructors must use reference parameters, because using value passing will cause infinite recursive calls.

If we pass values and parameters——

What may be puzzling here is why value passing and parameter passing are copy structures——

Passing values and parameters is to assign the value copy of the argument to the formal parameter and initialize you with the same type. In fact, it is a copy structure. In the following code, it can be observed by debugging that the copy constructor is entered first, and then the f(Date d) function is entered——

But by reference, d is an alias for d1.

  1. If not explicitly defined, the system generates a default copy constructor

🍓 This is a little different from the previous constructors and destructors——

  • For built-in type members, byte order copy (shallow copy) is completed
  • For a custom type member, its copy structure will be called

Let's verify:

You can see that the default constructor generated by the compiler does copy the byte order for the built-in type members. In other words, we can not write dates.

What about the stack? We don't write anything yet——

class Stack
{
public:
	//Constructor
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr)
		{
			cout << "malloc failed" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	//Destructor
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1(10);
	Stack st2(st1);
}

It collapsed 💩 ——

This is because——

Like this kind, we can't use the default one. We have to implement it ourselves.

For a custom type variable, its copy constructor is indeed called——

class A
{
public:
	A(const A& a)
	{
		cout << "A(const A&)" << endl;
	}

	A()
	{

	}
};

class Date
{
public:
	//Constructor
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;

	A _a;
};

int main()
{
	Date d1(2002, 3, 7);
	Date d2(d1);
	return 0;
}

5. Summary

There are too many details described above. It's really easy to faint. It's very clear to summarize here——

5.1 constructor

5.2 destructor

5.3 copy constructor

The end of this article @ Bian Tong Shu

Topics: C++ Back-end