📅  最后修改于: 2023-12-03 15:14:03.777000             🧑  作者: Mango
在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++中,浅拷贝和深拷贝都是常见的操作,但它们之间的区别很大。使用浅拷贝时,请注意对于指针类型的数据成员,不应该将其简单地赋值给新的对象。而是应该执行深拷贝。要执行深拷贝,请重写拷贝构造函数和赋值运算符,并为指针成员动态分配内存。了解这些将帮助你避免常见的错误,并正确地实现类的拷贝构造和赋值运算符。