C++: Classes and Objects: Initialization and Cleanup of Objects

1 Introduction:

Background on construction and destruction

1: Object-oriented in C++ comes from life, each object will have an initial value and clear data settings before the object is destroyed

2: Object initialization and cleanup are two very important security issues. An object or variable has no initial state, and the consequences of its use are unknown. Similarly, if an object or variable is not cleaned up in time, it will also cause certain security problems.

3: C++ uses constructors and destructors to solve the appeal problem. These two functions will be automatically called by the compiler to complete object initialization and cleanup. The initialization and cleanup of objects is what the compiler forces us to do, so If we don't provide constructors and destructors, the compiler will automatically provide them, but the compiler automatically provides empty items.

Constructor  

The main function is to assign values ​​to the member properties of the object when creating the object.

1 Syntax: class name () {}

2: Constructor, void cannot be written without return value

3: The function name is the same as the class name

4: The constructor can have parameters, so overloading can occur

5: When the program calls the object, it will automatically call the constructor, no need to call it manually, and it will only be called once

6: Permissions should be public

destructor

Mainly used before the object is destroyed, the system automatically calls to perform some cleaning work

1: Syntax: ~class name(){}

2: Destructor, no return value, no need to write void

3: The function name is the same as the class name, and the symbol ~ is added before the name

4: The destructor cannot have an adoption number, so overloading cannot be sent

5: The program will automatically call the destructor before the object is destroyed, no need to call it manually, and it will only be called once

6: Permission is public

sample code

#include<iostream>
#include<string>
using namespace std;


class Person {
public:
	Person(){
		cout << "Person The constructor call of" << endl;
	}

	~Person() {
		cout << "Person The destructor of the" << endl;
	}
};

int main() {
	Person p;
	return 0;
}

// Print
Person The constructor call of
Person The destructor of the

Here we can see that we just create an object and automatically execute the constructor and destructor. If our object is allocated on the stack, the destructor will be called automatically when the object is released.

2: Classification of constructors and their calls

2.1 Classification of constructors

1: According to the classification of parameters, it can be divided into: construction with parameters and construction without parameters. Construction without parameters is also called default constructor.

2: According to the type classification, it can be divided into: ordinary structure and copy structure.

class Person {
public:
	//no-argument (default) constructor
	Person() {
		cout << "no-argument constructor!" << endl;
	}
	//parameterized constructor
	Person(int a) {
		age = a;
		cout << "parameterized constructor!" << endl;
	}
	//copy constructor
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor!" << endl;
	}
	//destructor
	~Person() {
		cout << "destructor!" << endl;
	}
public:
	int age;
};

It can also be seen from the above code

1: As long as the constructor has no parameters, it is a parameterless constructor, and a parameterless constructor is the default constructor

2: As long as the constructor has parameters, then it is a parameterized constructor

3: The copy constructor is also a parameterized constructor, it just means that the parameter is an object of the same type

4: The parameter of the copy constructor is a reference, and at the same time, in order to prevent the content of the parameter from being changed. It needs to be prefixed with const to make it a constant reference.

5: As long as it is not a copy constructor, the rest are ordinary constructors.

2.2 Constructor call

There are three ways to call the constructor

1: Brackets

2: display method

3: Implicit conversion method

bracketing

#include <iostream>
using namespace std;

class Person {
public:
	//no-argument (default) constructor
	Person() {
		cout << "no-argument constructor!" << endl;
	}
	//parameterized constructor
	Person(int a) {
		age = a;
		cout << "parameterized constructor!" << endl;
	}
	//copy constructor
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor!" << endl;
	}
	//destructor
	~Person() {
		cout << "destructor!" << endl;
	}
public:
	int age;
};

void Test() {
	Person p1(10);
	Person p2(p1);

    Person p3();
}

int main() {
	Test();

	return 0;
}

// print result
 parameterized constructor
 copy constructor
 Destructor!
Destructor!

Note: Person p3(); ---- does not print the no-argument constructor

1: Because calling a parameterless constructor cannot add parentheses

2: If you add parentheses, the compiler thinks this is a function declaration, and other functions are allowed to be declared in the C++ function, so here should be a function p3() declared, and the return value type is Person, which does not accept formal parameters. Therefore, the constructor and destructor will not be called.

display method

#include <iostream>
using namespace std;

class Person {
public:
	//no-argument (default) constructor
	Person() {
		cout << "no-argument constructor!" << endl;
	}
	//parameterized constructor
	Person(int a) {
		age = a;
		cout << "parameterized constructor!" << endl;
	}
	//copy constructor
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor!" << endl;
	}
	//destructor
	~Person() {
		cout << "destructor!" << endl;
	}
public:
	int age;
};

void Test() {
	// display method
	Person p1 = Person(10);
	Person p2 = Person(p1);
}

int main() {
	Test();

	return 0;
}


// print result

parameterized constructor!
copy constructor!
destructor!
destructor!

let's look at an example

void Test() {
	// display method
	Person(10);
	cout << "aaaaaa" << endl;
}

// print result

parameterized constructor!
destructor!
aaaaaa

Person(10) -------> Written like this, the compiler will think that this is creating an anonymous object. After the current line ends, the destructor will execute immediately to recycle the anonymous object.

implicit conversion method

void Test() {
	// implicit conversion method
	Person p = 10; // Person p = Person(10);
	Person p1 = p; // Person p1 = Person(p);
}

// print result

parameterized constructor!
copy constructor!
destructor!
destructor!

3: When to call the copy constructor

There are usually three situations in which the copy constructor is called in C++

1: Use an already created object to initialize a new object

2: The way of value transfer is to pass values ​​to function parameters

3: Return the local object by value

1: Use an already created object to initialize a new object

#include <iostream>
using namespace std;

class Person {
public:
	//no-argument (default) constructor
	Person() {
		cout << "no-argument constructor!" << endl;
	}
	//parameterized constructor
	Person(int a) {
		age = a;
		cout << "parameterized constructor!" << endl;
	}
	//copy constructor
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor!" << endl;
	}
	//destructor
	~Person() {
		cout << "destructor!" << endl;
	}
public:
	int age;
};

void Test() {
	Person man(100); //p object has been created
	Person newman(man); //call the copy constructor
	Person newman2 = man; //copy construction

	Person newman3;
	newman3 = man; //Instead of calling the copy constructor, the assignment operation
}

int main() {
	Test();

	return 0;
}

2: The way of value passing is to pass values ​​to function parameters

#include <iostream>
using namespace std;

class Person {
public:
	//no-argument (default) constructor
	Person() {
		cout << "no-argument constructor!" << endl;
	}
	//parameterized constructor
	Person(int a) {
		age = a;
		cout << "parameterized constructor!" << endl;
	}
	//copy constructor
	Person(const Person& p) {
		age = p.age;
		cout << "copy constructor!" << endl;
	}
	//destructor
	~Person() {
		cout << "destructor!" << endl;
	}
public:
	int age;
};

// 2: Pass values ​​to function parameters by value
void dowork(Person p1) {
	
}

void Test() {
	Person p;  // call no-argument constructor
	dowork(p);
}

int main() {
	Test();

	return 0;
}

Running through the code, we found that a new object is defined when passed as a value, and then the constructor is called.

2: Return the local object by value

 

4: Constructor calling rules

By default, the C++ compiler adds at least 3 functions to a class

1: Default constructor (no parameters, function body is empty)

2: silent destructor (no parameters, function body is empty)

3: The default copy constructor, which copies the value of the attribute

4: If the user defines a parameterized constructor, C++ no longer provides a default parameterless constructor, but will provide a default copy function.

5: If the user defines a copy constructor, C++ will not provide other constructors (default constructor and parameterized constructor)

5: Deep copy and shallow copy

Shallow copy: Simple assignment copy operation.

Deep copy: Re-apply for space in the heap area and perform a copy operation.

Case: Assume that our person class not only has age, but also height. They can create corresponding objects by passing parameters through parameterized constructors. At the same time, we create a piece of memory in the heap area for the data of height, and release this piece of memory in the destructor.

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	// no-argument default constructor
	Person() {
		cout << "Calling the no-argument constructor!" << endl;
	}

	// parameterized constructor
	Person(int age, int height) {
		cout << "Calling a parameterized constructor!" << endl;
		this->m_age = age;
		this->m_height = new int(height);

	}

	// destructor
	~Person()
	{
		cout << "Calling the destructor!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}

public:
	int m_age;
	int* m_height;

};

void test() {
	Person p1(18, 180);
	Person p2(p1);
	cout << "p1 age of:" << p1.m_age << "  height:" << *p1.m_height << endl;
	cout << "p2 age of:" << p2.m_age << "  height:" << *p2.m_height << endl;
}

int main() {
	test();
	return 0;
}

 

You can see that the result of the operation will report an error:

The specific reason is: Two objects repeatedly release the same piece of memory in the heap area, resulting in illegal operations. See the picture below

Therefore, when doing destructuring, P2 first executes destructing operation and releases address 0x0011, while P1 will release 0x0011 when executing, which will cause the same block of memory address 0x0011 to be released repeatedly, which is an illegal operation.

solution:

If the attribute is opened in the heap area, you must provide a copy constructor yourself to ensure that the deep copy operation is performed (that is, another block of memory is opened in the heap area to store the corresponding data)

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	// no-argument default constructor
	Person() {
		cout << "Calling the no-argument constructor!" << endl;
	}

	// parameterized constructor
	Person(int age, int height) {
		cout << "Calling a parameterized constructor!" << endl;
		this->m_age = age;
		this->m_height = new int(height);

	}

	// copy constructor
	Person(const Person& p) {
		cout << "Call the copy constructor!" << endl;
		// If you do not use deep copy to open up new memory in the heap area, it will lead to the problem of repeated release of the heap area caused by shallow copy.
		m_age = p.m_age;
		m_height = new int(*p.m_height);
	}

	// destructor
	~Person()
	{
		cout << "Calling the destructor!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}

public:
	int m_age;
	int* m_height;

};

void test() {
	Person p1(18, 180);
	Person p2(p1);
	cout << "p1 age of:" << p1.m_age << "  height:" << *p1.m_height << endl;
	cout << "p2 age of:" << p2.m_age << "  height:" << *p2.m_height << endl;
}

int main() {
	test();
	return 0;
}

 

The print results are normal and there is no problem.

6: Initialization list

The constructor of C++ is mainly to initialize the property, now we can get the value of the initialized property in another way

#include<iostream>
using namespace std;
class Person {
public:
	// Traditional way to initialize
	/*Person(int a, int b, int c) {
		this->m_A = a;
		this->m_B = b;
		this->m_C = c;
	}*/

	// initialization list
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}

	// print initialized value
	void PrintPerson() {
		cout << "m_A :" << m_A << endl;
		cout << "m_B :" << m_B << endl;
		cout << "m_C :" << m_C << endl;
	}

private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {
	Person p(1, 2, 3);
	p.PrintPerson();
	return 0;
}

7: Class objects as class members

A member of a C++ class can be an object of another class, and we call this member an object member. For example: Class B has object A as a member, and A is an object member.

Case: Assume that there are human beings and mobile phone classes, and the objects of the mobile phone class are members of human beings

class Phone {}

class Person {

        Phone p;

}

question: When creating a Person object, which one comes first in the order of the constructor and destructor of Phone and Person?

answer: The order of the constructor is to call the constructor of the object member (Phone) first, and then call the constructor of this class (Person), the order of the destructor is exactly the opposite of the order of the constructor. (Call the original destructor first, then call the destructor of the object member).

#include<iostream>
#include<string>
using namespace std;
class Phone {
public:
	Phone(string pName) {
		this->m_phoneName = pName;
		cout << "transfer Phone constructor of" << endl;
	}

	~Phone()
	{
		cout << "transfer phone The destructor of" << endl;
	}


public:
	string m_phoneName;
};

class Person {
public:
	//The initializer list tells the compiler which constructor to call
	// m_phone(pName) is equivalent to: phone m_phone = pName (implicit conversion, that is, an object is implicitly constructed through parameterized construction)
	Person(string name, string PName) :m_Name(name), m_Phone(PName) {
		cout << "transfer Person Constructor" << endl;
	}

	~Person()
	{
		cout << "transfer Person The destructor of" << endl;
	}

	void playGame() {
		cout << m_Name << " use" << m_Phone.m_phoneName << "cell phone!" << endl;
	}

private:
	string m_Name;
	Phone m_Phone;

};

int main() {
	// When a member of a class is an object of another class, we call the member: object member
	// The order of construction: call the construction of object members first, and then call the construction of this class
	// Destruction order: opposite to the construction order (call the class destructor first, and then call the object member destructor)
	Person p("Zhang San", "Apple 13");
	p.playGame();
	return 0;
}

 

 

8: static members

Static members are the keyword static before member variables and member functions. Static members are divided into:

1: Static member variables

-------"All objects share the same data. As long as an object modifies this data, the data will be permanently changed.

------"Allocate memory in the compilation phase (allocated before the program runs), and store it in the global area

------" In-class declaration, out-of-class initialization.

2: Static member function

----->All objects share the same function.

----"Static member functions can only access static member variables.

Tags: C++

Posted by l053r on Sat, 17 Dec 2022 02:57:18 +0530