📜  C++中的浅拷贝和深拷贝(1)

📅  最后修改于: 2023-12-03 15:14:03.777000             🧑  作者: Mango

C++中的浅拷贝和深拷贝

在C++中,拷贝构造函数和赋值运算符是常见的操作。在进行这些操作时,涉及到的源对象和目标对象之间的拷贝可以是浅拷贝或深拷贝。在本文中,将介绍这两种拷贝方式的区别以及如何在类中实现深拷贝。

什么是浅拷贝?

浅拷贝是一种简单的拷贝方法,它只复制指针或引用,而不是整个对象。例如,下面这个类定义:

class Car {
public:
    Car() {
        std::cout << "Car constructor called" << std::endl;
    }

    Car(const Car& other) { // 拷贝构造函数
        std::cout << "Car copy constructor called" << std::endl;
        model_ = other.model_;
    }

    Car& operator=(const Car& other) { // 赋值运算符
        std::cout << "Car assignment operator called" << std::endl;
        model_ = other.model_;
        return *this;
    }

    void SetModel(const std::string& model) {
        model_ = model;
    }

private:
    std::string model_;
};

如果使用浅拷贝对Car对象进行拷贝构造或赋值操作,则只会复制指向model_成员的指针,而不会复制它所指向的实际数据。这导致了以下问题:

Car car1;
car1.SetModel("BMW");

Car car2 = car1; // 浅拷贝
std::cout << "Car1 model: " << car1.GetModel() << std::endl; // 输出:Car1 model: BMW
std::cout << "Car2 model: " << car2.GetModel() << std::endl; // 输出:Car2 model: BMW

car2.SetModel("Audi");
std::cout << "Car1 model: " << car1.GetModel() << std::endl; // 输出:Car1 model: Audi,不应该是BMW
std::cout << "Car2 model: " << car2.GetModel() << std::endl; // 输出:Car2 model: Audi

这是因为Car对象内部的model_成员变量是指向同一块内存的两个指针,它们在修改时是相互影响的。

什么是深拷贝?

与浅拷贝不同,深拷贝会创建一个新的对象,并将原始对象中的值复制到新对象中。这意味着新对象具有自己的内存,对其进行更改不会影响原始对象。

为了实现深拷贝,我们需要重写拷贝构造函数和赋值运算符。我们可以使用复制构造函数来执行深拷贝,如下所示:

class Car {
public:
    Car() {
        std::cout << "Car constructor called" << std::endl;
    }

    Car(const Car& other) { // 拷贝构造函数
        std::cout << "Car copy constructor called" << std::endl;
        model_ = other.model_;
    }

    Car& operator=(const Car& other) { // 赋值运算符
        std::cout << "Car assignment operator called" << std::endl;
        if (this != &other) {
            model_ = other.model_;
        }
        return *this;
    }

    void SetModel(const std::string& model) {
        model_ = model;
    }

private:
    std::string model_;
};

class Person {
public:
    Person() {
        std::cout << "Person constructor called" << std::endl;
    }

    Person(const Person& other) { // 拷贝构造函数-深拷贝
        std::cout << "Person copy constructor called" << std::endl;
        name_ = other.name_;
        car_ = new Car(*other.car_); // 创建新的Car对象进行拷贝
    }

    ~Person() { // 析构函数-释放内存
        delete car_;
    }

    Person& operator=(const Person& other) { // 赋值运算符-深拷贝
        std::cout << "Person assignment operator called" << std::endl;
        if (this != &other) {
            name_ = other.name_;
            delete car_;
            car_ = new Car(*other.car_); // 创建新的Car对象进行拷贝
        }
        return *this;
    }

    void SetName(const std::string& name) {
        name_ = name;
    }

    void SetCar(const Car& car) {
        *car_ = car;
    }

private:
    std::string name_;
    Car* car_;
};

在上面的示例中,我们将Car对象指针存储在Person类中,并使用new运算符为它分配新内存。在拷贝构造函数和赋值运算符中,我们使用new运算符创建新对象并将其指针分配给类成员,然后使用*运算符对要复制的Car对象进行解引用,从而调用Car类的复制构造函数,执行深拷贝。最后,我们在析构函数中释放内存。

现在,如果使用深拷贝对Person对象进行拷贝构造或赋值操作,则会创建一个新的Car对象,而不是指向原始对象的指针。这在下面的示例中得到展示:

Car car;
car.SetModel("BMW");

Person person1;
person1.SetName("Tom");
person1.SetCar(car);

Person person2 = person1; // 深拷贝
person2.SetName("Jerry");
person2.GetCar().SetModel("Audi");
std::cout << "Person1 name: " << person1.GetName() << ", car model: " << person1.GetCar().GetModel() << std::endl;
std::cout << "Person2 name: " << person2.GetName() << ", car model: " << person2.GetCar().GetModel() << std::endl;

输出:

Person constructor called
Car constructor called
Person constructor called
Person copy constructor called
Person constructor called
Car copy constructor called
Person assignment operator called
Car assignment operator called
Person1 name: Tom, car model: BMW
Person2 name: Jerry, car model: Audi

可以看到,如果将person2的Car对象的模型更改为Audi,则不会影响person1的Car对象的模型,因为它们都有自己的内存空间。

总结

在C++中,浅拷贝和深拷贝都是常见的操作,但它们之间的区别很大。使用浅拷贝时,请注意对于指针类型的数据成员,不应该将其简单地赋值给新的对象。而是应该执行深拷贝。要执行深拷贝,请重写拷贝构造函数和赋值运算符,并为指针成员动态分配内存。了解这些将帮助你避免常见的错误,并正确地实现类的拷贝构造和赋值运算符。