C++指南——拷贝构造函数和赋值运算符重载

发布时间:2024年01月08日

1 举个例子(问题由来)

在C++中,使用Car car2 = car1; 这种形式的语句被称为拷贝构造(用一个对象初始化一个新对象),它并不总是会调用构造函数。这个语法实际上是使用拷贝构造函数来创建一个新对象 car2,并将其初始化为另一个对象 car1 的副本。如果你没有提供自定义的拷贝构造函数,C++ 会生成一个默认的拷贝构造函数,该函数会逐个成员进行复制。

而对于 Car car2; car2 = car1;,这是两个步骤的组合。首先,Car car2; 创建了一个未初始化的对象 car2,然后 car2 = car1; 执行了赋值操作,这会调用赋值运算符 (operator=)。如果你没有提供自定义的赋值运算符,C++ 会生成默认的赋值运算符,该运算符也会逐个成员进行复制。

所以,两者的行为略有不同。前者是在对象初始化的时候就进行了拷贝构造,而后者是在对象已经创建后再进行了赋值操作。如果你的类有复杂的资源管理或者需要进行深拷贝,你可能需要实现自定义的拷贝构造函数和赋值运算符来确保正确的行为。(就是说,成员的浅拷贝不合法时,可以对拷贝构造函数和赋值运算符函数进行定义)

2 拷贝构造函数与构造函数不同

  1. 构造函数 (Constructor): 构造函数是在创建对象时调用的特殊成员函数。它用于初始化对象的各个成员变量,为对象分配资源,或执行其他必要的初始化操作。构造函数通常在对象被声明时自动调用。

    class Car {
    public:
        // 构造函数
        Car() {
            // 初始化操作
        }
    };
    
    // 创建对象时构造函数被调用
    Car car1;
    
  2. 拷贝构造函数 (Copy Constructor): 拷贝构造函数是一种特殊的构造函数,用于创建一个对象并将其初始化为另一个对象的副本。拷贝构造函数通常在以下情况下调用:

    • 通过值传递的方式传递对象给函数。
    Car mycar;
    func1(mycar);
    ....
    
    //func1的定义
    void func1(car c){.....}
    
    • 通过值返回对象。(该情况可能赋值运算符也会被调用)
Car mycar;
mycar = func2(); //赋值运算符被调用
Car mycar3 = func2() //拷贝构造函数被调用

//func2的定义
Car func(){Car c;....return c;}
  • 通过另一个对象初始化一个新对象。
class Car {
public:
    // 拷贝构造函数
    Car(const Car& other) {
        // 复制 other 的成员变量到当前对象
    }
};

// 使用拷贝构造函数创建 car2,并将其初始化为 car1 的副本
Car car2 = car1;

总结来说,构造函数用于对象的初始化,而拷贝构造函数用于在创建对象时将其初始化为另一个对象的副本。拷贝构造函数的存在使得对象能够在不同的上下文中进行复制,并且通常在涉及到对象的复制或传递时起到关键作用。

3 默认拷贝构造函数是浅拷贝

C++ 默认生成的拷贝构造函数执行的是浅拷贝(shallow copy)。浅拷贝意味着拷贝构造函数只是逐个成员地复制源对象的成员变量的值到目标对象,而不考虑这些成员变量的内部结构。

如果类中包含指针或动态分配的资源,浅拷贝可能导致问题,因为两个对象将指向相同的内存区域。这意味着如果其中一个对象修改了共享的资源,另一个对象也会受到影响。

为了避免这个问题,当类中包含指针或动态分配的资源时,通常需要手动实现自定义的拷贝构造函数来执行深拷贝(deep copy)。深拷贝会为目标对象分配新的内存,并将源对象的数据复制到新的内存中,以确保两个对象相互独立

以下是一个示例,展示了使用默认生成的拷贝构造函数可能引起的问题:

#include <iostream>

class DynamicArray {
public:
    DynamicArray(int size) {
        size_ = size;
        data_ = new int[size_];
    }

    ~DynamicArray() {
        delete[] data_;
    }

private:
    int* data_;
    int size_;
};

class MyClass {
public:
    // 默认生成的拷贝构造函数是浅拷贝
    // 这可能导致两个对象共享相同的内存,造成问题
    // 需要手动实现深拷贝
    MyClass(const MyClass& other) {
        // 默认生成的拷贝构造函数执行的是浅拷贝
        // data_ 指针将被复制,两个对象将共享相同的内存
        data_ = other.data_;
    }

private:
    DynamicArray* data_;
};

int main() {
    MyClass obj1(5);
    MyClass obj2 = obj1; //拷贝构造函数被调用

    // 此时 obj1 和 obj2 共享相同的 DynamicArray 对象
    // 修改其中一个对象可能会影响另一个对象
    return 0;
}

在上述示例中,如果 MyClass 的对象通过默认生成的拷贝构造函数进行复制,它们将共享相同的 DynamicArray 对象,可能导致潜在的问题。

4 默认赋值运算符是浅拷贝

C++ 默认生成的赋值运算符 (operator=) 也执行浅拷贝。这意味着默认情况下,赋值运算符只是逐个成员地复制源对象的成员变量的值到目标对象,而不考虑这些成员变量的内部结构。

如果类中包含指针或动态分配的资源,浅拷贝可能导致问题,因为两个对象将指向相同的内存区域。对其中一个对象的修改可能会影响另一个对象。

与拷贝构造函数一样,当类中包含指针或动态分配的资源时,通常需要手动实现自定义的赋值运算符来执行深拷贝,以确保两个对象相互独立

以下是一个示例,展示了使用默认生成的赋值运算符可能引起的问题:

#include <iostream>

class DynamicArray {
public:
    DynamicArray(int size) {
        size_ = size;
        data_ = new int[size_];
    }

    ~DynamicArray() {
        delete[] data_;
    }

private:
    int* data_;
    int size_;
};

class MyClass {
public:
    // 默认生成的赋值运算符是浅拷贝
    // 这可能导致两个对象共享相同的内存,造成问题
    // 需要手动实现深拷贝
    MyClass& operator=(const MyClass& other) {
        // 默认生成的赋值运算符执行的是浅拷贝
        // data_ 指针将被复制,两个对象将共享相同的内存
        if (this != &other) {
            data_ = other.data_;
        }
        return *this;
    }

private:
    DynamicArray* data_;
};

int main() {
    MyClass obj1(5);
    MyClass obj2;
    
    // 使用默认生成的赋值运算符进行赋值
    obj2 = obj1;

    // 此时 obj1 和 obj2 共享相同的 DynamicArray 对象
    // 修改其中一个对象可能会影响另一个对象
    return 0;
}

在上述示例中,如果 MyClass 的对象通过默认生成的赋值运算符进行赋值,它们将共享相同的 DynamicArray 对象,可能导致潜在的问题。

文章来源:https://blog.csdn.net/New_Teen/article/details/135466918
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。