C++ constructors and destructors

1. Introduction to constructor

In C++, a constructor is a special member function that is called automatically when a new object of the class is created. The main purpose of a constructor is to initialize an object of a class.

Here is some important information about constructors:

1. The name of the constructor : The name of the constructor is exactly the same as the class name.

2. Automatic call : There is no need to explicitly call the constructor, it will be called automatically when the object is created. Likewise, destructors ( ~functions with a tilde in front of the class name) are called automatically when the object exceeds its lifetime.

3. No return type : The constructor has no return type, voidnot even .

4. Can be overloaded : Just like other functions, constructors can also be overloaded, as long as the number or types of parameters are different.

5. There can be default parameters : the constructor can have default parameters. If no arguments are provided, these default values ​​will be used.

6. Type constructor : If the constructor has only one parameter, it can also be used for implicit conversion.

For example:

class Rectangle {
    public:
    int width;
    int height;

    // 这是一个构造函数
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }
};

// 使用构造函数创建 Rectangle 对象
Rectangle rect(10, 5);

In this example, Rectanglethe class has a constructor that takes two parameters wand h, and assigns the values ​​of those parameters to member variables widthand , respectively height. Then when creating Rectangleobject rect, we use the constructor and provide values ​​for widthand height.

2. Constructor: type constructor

A type constructor (also known as a converting constructor) is a special type of constructor that takes only one argument. Type constructors allow objects to be implicitly converted to the type of their class when they are initialized or assigned . This conversion constructor defines a conversion method from a specific type to the class type.

For example, suppose we have a Fractionclass called that represents a fraction, with a numerator and a denominator. We might want to be able to create one directly from an integer Fraction, where the numerator is this integer and the denominator is 1. intWe can do this by defining a constructor that accepts a parameter:

class Fraction {
private:
    int numerator;
    int denominator;

public:
    // 类型构造函数
    Fraction(int num) : numerator(num), denominator(1) {}

    // 通常的构造函数
    Fraction(int num, int denom) : numerator(num), denominator(denom) {}

    // 其他成员函数...
};

With this type constructor in place, we can initialize a Fractionobject like this:

Fraction frac1 = 5;  // 使用类型构造函数,等价于 Fraction frac1(5);
Fraction frac2(7, 2);  // 使用通常的构造函数

In the code above, it is initialized frac1with an integer . 5The compiler implicitly converts this integer to type using the type constructor Fraction.

It should be noted that this implicit conversion may cause some unexpected behaviors, so if you do not want the constructor to perform implicit conversion, you can add the keyword before the constructor, which requires explicit Converts to an explicit constructor . For example: explicit

class Fraction {
private:
    int numerator;
    int denominator;

public:
    // 显式类型构造函数
    explicit Fraction(int num) : numerator(num), denominator(1) {}

    // 通常的构造函数
    Fraction(int num, int denom) : numerator(num), denominator(denom) {}

    // 其他成员函数...
};

In this example, the following code will no longer be allowed because we have prevented implicit conversions:

Fraction frac1 = 5;  // 编译错误:不能隐式转换

If we want to create a Fractionobject from an integer, we have to call the constructor explicitly:

Fraction frac1(5);  // OK:显式地调用构造函数

3. Constructor: copy constructor

In C++, a copy constructor is a special constructor used to create a new copy of an object. The copy constructor accepts a reference to an object of the same type as a parameter, and then copies the value of this object.

Here are some important information about copy constructors:

  1. Declaration of the copy constructor : The general form of the copy constructor is ClassName(const ClassName& obj), where ClassNameis the name of the class and objis a reference to the passed object of the same type.

  2. When to call : The copy constructor is called in the following situations: when a new object is created as a copy of an existing object; when an object is passed by value as a parameter of a function; when a function returns an object.

  3. Default copy constructor : If you do not define a copy constructor for the class, the compiler will automatically generate a default copy constructor for you. The default copy constructor will perform a shallow copy of all member variables. If the members of the class contain pointers or dynamically allocated memory, you may need to define your own copy constructor to perform a deep copy.

Here is an example of a simple class with a copy constructor:

class MyClass {
private:
    int* data;

public:
    // 构造函数,用于初始化动态分配的内存
    MyClass(int size) {
        data = new int[size];
        // 初始化数据...
    }

    // 拷贝构造函数
    MyClass(const MyClass& other) {
        // 这是一个简单的例子,实际情况可能更复杂
        // 你可能需要复制 'other.data' 中的实际数据
        data = new int[sizeof(other.data)];
        memcpy(data, other.data, sizeof(other.data));
    }

    // 析构函数,用于释放动态分配的内存
    ~MyClass() {
        delete[] data;
    }

    // 其他成员函数...
};

In this example, MyClassthere is a dynamically allocated intarray member data. The copy constructor of this class creates a new intarray and datacopies the contents of the original object's members into the new array. This is called a deep copy because it copies the data itself, not just the pointers.

Note that if your class has dynamically allocated memory or other resources that require special handling, you may need to define your own copy constructor to ensure that these resources are copied correctly. Otherwise, if you rely on the default copy constructor automatically generated by the compiler, only a shallow copy may be made. This can cause problems like memory leaks or dangling pointers.

Deep copy and shallow copy: In C++, deep copy and shallow copy are two different object copy methods, and their main difference is how to deal with the pointer members of the object.

Shallow copy : When we perform a shallow copy, only all non-static member variables of the object are copied to the new object. If the member variable contains a pointer, the pointer value (that is, the address) is copied, not what the pointer points to. This means that the original object and the copied object share the same dynamic memory. This can cause problems, for example, when an object is deleted, its destructor may delete shared memory, making another object's pointer a dangling pointer.

Deep copy : When we perform a deep copy, we not only copy all non-static member variables of the object, but also create a new memory copy for each pointer member of dynamic memory allocation, ensuring that the new object gets a brand new copy of the original object data, rather than referencing the same memory. In this way, the two objects will not share memory, avoiding possible problems with shallow copies.

If your class's member variables contain only primitive types or other value-semantic types (eg std::string, std::vectoretc.), you can usually rely on the default copy constructor generated by the compiler to do a shallow copy. However, if the class has pointer members to dynamically allocated memory, or contains resources that require special handling (such as file handles, network connections, etc.), then it is usually necessary to provide a custom copy constructor to perform a deep copy. Otherwise, you may encounter problems such as memory leaks, dangling pointers, etc.

Fourth, the destructor

In C++, a destructor is a special member function that cleans up an object at the end of its lifetime. When an object is about to be destroyed, whether because it left the scope of its definition, or because it was dynamically allocated memory and was destroyed delete, its destructor is called.

Here is some important information about destructors:

  1. Name of the destructor : The name of the destructor is the same as the class name, but preceded by a tilde ~.

  2. Automatic call : There is no need to explicitly call the destructor, it will be called automatically when the object is destroyed.

  3. No return type, no parameters : A destructor has no return type and no parameters. This means you cannot overload destructors.

  4. Purpose : Destructors are often used to release resources an object may own. For example, if an object has a pointer member to dynamically allocated memory, the destructor may need to delete that memory. Failure to do so can result in memory leaks.

Here is an example of a simple class with a destructor:

class MyClass {
private:
    int* data;

public:
    // 构造函数,用于初始化动态分配的内存
    MyClass(int size) {
        data = new int[size];
        // 初始化数据...
    }

    // 析构函数,用于释放动态分配的内存
    ~MyClass() {
        delete[] data;
    }

    // 其他成员函数...
};

In this example, MyClassthere is a dynamically allocated intarray member data. When an MyClassobject is destroyed, its destructor deletes the array, preventing memory leaks.

Note that if your class has an inheritance relationship, the destructor should usually be declared as virtual. This way, when a base class pointer to a derived class object is deleted, the correct destructor is called. If the destructor is not virtual, then probably only the destructor of the base class will be called, resulting in the resources of the derived class not being properly cleaned up.

Five, constructor and destructor call sequence

The order in which constructors and destructors are called is very well-defined in C++ and is closely related to object creation and destruction.

1. The calling order of the constructor :

  1. First, the constructor of the base class is called.

  2. Then, the constructors for the member variables of the derived class are called in the order in which they appear in the class definition.

  3. Finally, the constructor of the derived class is called.

2. The calling order of the destructor :

The order in which destructors are called is the exact opposite of the order in which constructors are called.

  1. First, the destructor of the derived class is called.

  2. The destructors for the member variables of the derived class are then called in the reverse order of the order in which they appear in the class definition.

  3. Finally, the destructor of the base class is called.

This ordering ensures that resource acquisition and release are correct and efficient throughout the object's lifetime. In the construction phase, first build the base class and member variables, and then build the derived class. In the destructor phase, the derived class is destroyed first, and then the member variables and the base class are destroyed, so as to prevent the situation that member variables or base classes may still need to be accessed in the destructor of the derived class.

Here's a simple example to illustrate this order:

#include <iostream>

class Base {
public:
    Base() { std::cout << "Base Constructor\n"; }
    virtual ~Base() { std::cout << "Base Destructor\n"; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived Constructor\n"; }
    ~Derived() { std::cout << "Derived Destructor\n"; }
};

int main() {
    Derived d;
    return 0;
}

The output of this code will be:

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

This output clearly shows the order in which constructors and destructors are called.

Guess you like

Origin blog.csdn.net/2201_75772333/article/details/130476037