C++ class and object construction and destructor, deep copy shallow copy problem

1. Constructor and destructor

C++ uses constructors and destructors to solve the above problems. These two functions will be automatically called by the compiler to complete object initialization and cleanup.

The initialization and cleanup of the object are things that the compiler is forced to do. It does not provide constructors and destructors, and the compiler will provide constructors and destructors with empty implementations.

1.1 Constructor function:

The main function is to assign values ​​to the member properties of the object when creating the object. The constructor is automatically called by the compiler without manual call

grammar:

className (){}

1. The constructor has no return value and does not write void

2. The function name is the same as the class name

3. The constructor can have parameters and can be overloaded

4. The program will automatically call the constructor when calling the object , no need to call it manually, and it will only be called once

example:

#include <iostream>
using namespace std;
class Person
{
public:
        Person()
        {
                cout  <<  "构造函数的调用" << endl;
        }
 
};
void test01()
{
        Person  p;
}
int main()
{
        test01();
        return 0;
}
<<构造函数的调用

1.2 The role of the destructor:

The main function is that the system automatically calls before the object is destroyed to perform some cleaning work

grammar:

~className(){}

1. The destructor has no return value and does not write void

2. The function name is the same as the class name, and the symbol is added before the name ~

3. The destructor cannot have parameters, so overloading cannot occur

4. 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

#include <iostream>
using namespace std;
class Person
{
public:
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
};
void test01()
{
        Person  p;
}
int main()
{
        test01();
        return 0;
}
<<析构函数调用

2. Classification and calling method of constructor

Constructors can be categorized by different types

According to parameters, it can be divided into parameterized construction and non-parametric construction

It can also be divided into copy construction and ordinary construction according to type

Copy construction is to completely assign an object property to another object, but the original object cannot be changed, so const is added

Copy constructor syntax:

classname(const classname & objectname){}

2.1 Bracket method

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2(10);
        Person  p3(p2);
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用

output the age of p3

cout << "p3的年龄:" << p3.age << endl;
<<p3的年龄:10

Ps: Constructing a default function using the parenthesis method cannot add parentheses, because this will be considered a function declaration

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1();
        Person  p2(10);
        Person  p3(p2);
        cout  <<  "p3的年龄:" << p3.age << endl;
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用

2.2 Display method

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2 = Person(10);
        Person  p3 = Person(p2);
        cout  <<  "p3的年龄:" << p3.age << endl;
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
p3的年龄:10
析构函数的调用
析构函数的调用
析构函数的调用

PS1: Anonymous object: directly write Person(10); it is called anonymous object, feature: when the current line ends, the system will immediately recycle the anonymous object, that is, call the destructor immediately

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person(10);
        cout  << "--------------------------"  << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
析构函数的调用
--------------------------
析构函数的调用

Ps2: Do not use the copy function to initialize anonymous objects , the compiler will think that Person (p3) is equivalent to Person p3 calling the default construction, that is, the no-argument construction

2.3 Implicit conversion method

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void test01()
{
        Person  p1;
        Person  p2 = 10;
        Person  p3 = p2;        
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
有参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用
析构函数的调用

3. When to use the copy constructor

3.1 Use an already created object to initialize a new object

This has already been done before, that is, to directly call the copy constructor

3.2 Ways of passing values ​​Passing values ​​to function parameters

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
void do_work(Person p)
{
 
}
void test01()
{
        Person  p;
        do_work(p);
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用

3.3 Returning local objects by value

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
Person do_work()
{
        Person  p1;
        return p1;
}
void test01()
{
          Person p = do_work();
}
int main()
{
        test01();
        return 0;
}
<<
无参构造函数的调用
拷贝构造函数的调用
析构函数的调用
析构函数的调用

PS: Different compilers may have different results. In gcc, there will be no copy function call, and the address will be the same when you print the address.

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
Person do_work()
{
        Person  p1(10);
        cout  << (int*)&p1  << endl;
        return p1;
}
void test01()
{
          Person p = do_work();
          cout << (int*)&p  << endl;
          cout << p.age  << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
0x7ffebf3bf96c
0x7ffebf3bf96c
10
析构函数的调用

Fourth, the constructor call rules

By default: the c++ compiler will add at least 3 functions to a class constructor

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

2. Destructor (no parameters, function body is empty)

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

The constructor call rules are as follows:

1. User -defined constructor with parameters , C++ no longer provides default no-argument construction , but will provide default copy construction

2. User- defined copy construction , c++ will no longer provide other constructors

It can be recorded as: copy construction > construction with parameters > construction without parameters

4.1 Comment out the copy construction and provide custom construction with parameters and without parameters

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        Person()
        {
                cout  <<  "无参构造函数的调用" << endl;
        }
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        //Person(const Person &p)
        //{
        //        age  = p.age;
        //        cout  << "拷贝构造函数的调用" << endl;
        //}
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
void test01()
{
          Person p1(10);
          Person p2(p1);
          cout << "p2的年龄:" << p2.age << endl;
        
}
int main()
{
        test01();
        return 0;
}
<<
有参构造函数的调用
p2的年龄:10
析构函数的调用
析构函数的调用

Compared with the previous custom copy construction, we can see that it provides a default copy construction , but the output "copy constructor call" written by myself is missing, and the destructor is still called

It can be understood that every object will call the destructor when it is used up

4.2 Comment out the copy construction, no parameter construction, and provide a custom parameter construction

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        //Person()
        //{
        //        cout  << "无参构造函数的调用" << endl;
        //}
        //有参构造函数
        Person(int a)
        {
                age  = a;
                cout  <<  "有参构造函数的调用" << endl;
        }
        //拷贝构造
        //Person(const Person &p)
        //{
        //        age  = p.age;
        //        cout  << "拷贝构造函数的调用" << endl;
        //}
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
void test01()
{
          Person p;
}
int main()
{
        test01();
        return 0;
}
<<会报错,提示你没有默认的无参构造

Combining 4.1 and 4.2, we can understand that if you provide a custom construction with parameters, there will still be a default copy, but there will be no more defaults without parameters.

4.3 Annotate no-argument construction and argument construction, provide custom copy construction

#include <iostream>
using namespace std;
class Person
{
public:
        
        //无参构造函数
        // Person()
        // {
        //          cout << "无参构造函数的调用"  << endl;
        // }
        //有参构造函数
        // Person(int a)
        // {
        //          age = a;
        //          cout << "有参构造函数的调用"  << endl;
        // }
        //拷贝构造
        Person(const Person &p)
        {
                age  = p.age;
                cout  <<  "拷贝构造函数的调用" << endl;
        }
        
        //析构函数
        ~Person()
        {
                cout  <<  "析构函数的调用" << endl;
        }
 
        int age;
};
 
 
void test01()
{
          Person p;
          person p1(10);
}
int main()
{
        test01();
        return 0;
}
<<35,36行都报错

In this case, a custom copy function is provided, so the default no-argument construction and argument construction are not provided

Five, deep copy and shallow copy

5.1 What is deep copy and shallow copy?

Shallow copy: simple assignment copy operation

Deep copy: re-apply for space in the heap area and perform copy operations

5.2 Why is deep copy needed?

After applying for space in the heap area, the programmer needs to manually release the allocated space. This is the problem of repeated memory release if deep copy is not used.

example:

#include <iostream>
using namespace std;
class Person
{
public:
    
    //无参构造函数
    Person()
    {
        cout << "无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a, int  height)
    {
        age = a;
        m_height  = new int(height);
        cout << "有参构造函数的调用" << endl;
    }
    
    //析构函数
    ~Person()
    {
        cout << "析构函数的调用" << endl;
    }
    int age;
    int *m_height;
};
void test01()
{
     Person p1(18, 170);
     cout <<  "p1的年龄:" <<  p1.age << "  p1的身高:" << *p1.m_height  << endl;
     Person p2(p1);
     cout <<  "p2的年龄:" <<  p2.age << "  p2的身高:" << *p2.m_height  << endl;
}
int main()
{
    test01();
    return 0;
}
<<
有参构造函数的调用
p1的年龄:18  p1的身高:170
p2的年龄:18  p2的身高:170
析构函数的调用
析构函数的调用

If it is not released manually, no error is reported

But in order to standardize, we need to manually release, and an error will be reported. The manual release process is written in the destructor, following the first-in-last-out rule, first release p2, and then release p1

Reason analysis: Using Person p2 (p1) is equivalent to using the copy function automatically provided by the compiler to perform a shallow copy operation. In the end, the m_height memory of p2 will be released first and then the m_height of p1 will be released, which leads to the problem of repeated release .

5.3 Solution: use deep copy

Concrete custom copy constructor

#include <iostream>
using namespace std;
class Person
{
public:
    
    //无参构造函数
    Person()
    {
        cout << "无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a, int  height)
    {
        age = a;
        m_height  = new int(height);
        cout << "有参构造函数的调用" << endl;
    }
    //自定义拷贝构造函数解决浅拷贝重复释放内存空间问题
    Person(const Person &p)
    {
        cout << "拷贝构造函数调用" << endl;
        age = p.age;
        //m_height  = p.m_height;这行其实就是默认拷贝构造函数的身高浅拷贝
        m_height  = new int(*p.m_height);
    }
    //析构函数
    ~Person()
    {
        if(m_height  != NULL)
        {
             delete m_height;
             m_height = NULL;
        }
        cout << "析构函数的调用" << endl;
    }
    int age;
    int *m_height;
};
void test01()
{
     Person p1(18, 170);
     cout <<  "p1的年龄:" <<  p1.age << "  p1的身高:" << *p1.m_height  << endl;
     Person p2(p1);
     cout <<  "p2的年龄:" <<  p2.age << "  p2的身高:" << *p2.m_height  << endl;
}
int main()
{
    test01();
    return 0;
}
<<
有参构造函数的调用
p1的年龄:18  p1的身高:170
拷贝构造函数调用
p2的年龄:18  p2的身高:170
析构函数的调用
析构函数的调用

Ps: If there are properties created in the heap area, you must provide a copy constructor yourself to prevent problems caused by shallow copying.

Guess you like

Origin blog.csdn.net/SL1029_/article/details/129556509