【发布时间】:2014-04-23 11:12:30
【问题描述】:
来自 Bjarne Stroustrup 的 C++ 编程语言第 4 版:
3.3.4。抑制操作
对层次结构中的类使用默认复制或移动通常是一场灾难: 只给出一个指向基址的指针,我们根本不知道派生的成员是什么 类有(§3.2.2),所以我们不知道如何复制它们。所以,最好的办法 do 通常是删除默认的复制和移动操作,也就是 消除这两个操作的默认定义:
class Shape {
public:
Shape(const Shape&) =delete; // no copy operations
Shape& operator=(const Shape&) =delete;
Shape(Shape&&) =delete; // no move operations
Shape& operator=(Shape&&) =delete;
~Shape();
// ...
};
为了理解他的意思,我创建了以下示例:
#include <iostream>
using namespace std;
class Person {
private:
int age;
public:
Person(const int& Age) : age {Age} {};
Person(const Person& from) : age {from.Age()} { cout << "copy constructor" << endl; };
Person& operator=(const Person& from) { cout << "copy assignment" << endl; age = from.Age(); return *this; }
virtual void Print() { cout << age << endl; };
void Age(const int& Age) { age = Age; };
int Age() const { return age; };
};
class Woman : public Person {
private:
int hotness;
public:
Woman(const int& Age, const int& Hotness) : Person(Age), hotness {Hotness} {};
Woman(const Woman& from) : Person(from), hotness {from.Hotness()} { cout << "copy constructor of woman" << endl; };
Woman& operator=(const Woman& from) { Person::operator=(from); cout << "copy assignment of woman" << endl; hotness = from.Hotness(); return *this; };
void Print() override { cout << Age() << " and " << hotness << endl; };
int Hotness() const { return hotness; };
};
int main() {
Woman w(24, 10);
Person p = w;
p.Print();
return 0;
}
这个版本的程序的输出是:
copy constructor
24
作为一个菜鸟,这对我来说有点意外,但后来 a 意识到由于 p 不是指针,因此不使用虚拟表,并且由于它是 Person,因此调用了 Person::Print() .所以我知道 Person 的复制构造函数被调用了,但我不知道 Woman 的复制构造函数是否被调用,但这并不重要,因为 p 是一个 Person,通过它我永远无法访问对女人::性感,即使我尝试了演员表。
所以我认为他可能只是在谈论指针,所以我尝试了这个:
int main() {
Woman w(24, 10);
Person* p = new Person(20);
p->Print();
p = &w;
p->Print();
return 0;
}
新的输出是:
20
24 and 10
现在 p 是一个指针,因为它是一个指针,所以不会进行复制或移动,只需更改引用。
然后我想我可以尝试取消引用 p 并将 w 分配给它:
int main() {
Woman w(24, 10);
Person* p = new Person(20);
p->Print();
*p = w;
p->Print();
return 0;
}
输出是这样的:
20
copy assignment
24
我认为第二次调用 p->Print() 会调用 Woman::Print(),因为 p 指向的是一个女人,但事实并非如此。知道为什么吗?来自 Person 的复制分配被调用,我认为是因为 p 是 Person*。
然后我尝试了这个:
int main() {
Woman w(24, 10);
Person* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();
return 0;
}
新的输出是这样的:
20 and 7
copy assignment
24 and 7
所以我猜是因为 p 是 Person*,所以调用了 Person 的副本分配,而不是 Woman 的副本分配。奇怪的是,年龄更新了,但热度值保持不变,我不知道为什么。
再试一次:
int main() {
Woman w(24, 10);
Woman* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();
return 0;
}
输出:
20 and 7
copy assignment
copy assignment of woman
24 and 10
现在数字似乎是对的。
我的下一步是删除 Person 的复制分配的实现,看看是否会调用默认值:
//Person& operator=(const Person& from) { cout << "copy assignment" << endl; age = from.Age(); return *this; }
输出:
20 and 7
copy assignment of woman
24 and 10
请注意,年龄是复制的,所以不用担心。
下一个明显的举措是删除女人的复制分配的实现,看看会发生什么:
//Woman& operator=(const Woman& from) { Person::operator=(from); cout << "copy assignment of woman" << endl; hotness = from.Hotness(); return *this; };
输出:
20 and 7
24 and 10
一切似乎都很好。
所以现在我不太明白作者的意思,所以如果有人能帮助我,我将不胜感激。
谢谢。
密送。
【问题讨论】:
-
作者建议复制构造函数的默认行为是浅拷贝,这通常不是您想要的(特别是如果您的类包含指针)。从本质上讲,他是说除非您明确希望您的类是可复制的并定义自己的复制构造函数,否则您应该删除它以防止人们使用默认生成的浅拷贝并期望它生成正确的副本。
-
一个大问题是,如果你的类使用指针,一个默认副本将具有相同的指针值,所以你的两个类实例将指向内存中的相同空间,这意味着更改值一个会改变另一个。另外,你的帖子有点长,你应该看看你能不能把它剪掉一点或者做一个 tl;dr 部分。
-
@Namfuak 这将是浅拷贝和深拷贝之间的区别。换句话说,浅拷贝基本上是
for arg in class.args: new_class.arg = class.arg,没有比这更聪明的了。 -
@aruisdante 我知道如果你的类是一个资源句柄,你应该要么实现深拷贝,要么删除默认的拷贝构造函数。但是作者所说的与基类上的默认复制构造函数有关,基类是层次结构的一部分。我只是不太明白他在担心什么,因为在我的测试之后我注意到一个基类的默认复制构造函数是自动调用的。
-
因为
Base(Inherited) // Base = Inherited是一种有效的做法,并且可能不会产生预期的结果。在vector<Base>等容器中使用它时,最常发生这种情况。 IE。vector<Base>[0] = Inherited会起作用,但不会做你期望它做的事情,即在向量中有一个Inherited的实例。它会简单地创建一个Base,其中包含来自Inherited浅层复制的公共字段。
标签: c++ pointers copy-constructor