gpt4 book ai didi

C++ 为什么我应该抑制默认的复制构造函数?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:07:30 24 4
gpt4 key购买 nike

来自 Bjarne Stroustrup 的 C++ 编程语言第 4 版:

3.3.4。抑制操作

对层次结构中的类使用默认复制或移动通常是一场灾难:只给出一个指向基的指针,我们根本不知道派生的成员是什么类有(§3.2.2),所以我们不知道如何复制它们。所以,最好的办法通常做的是删除默认的复制和移动操作,即消除这两个操作的默认定义:

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

作为菜鸟,这对我来说有点意外,但后来我意识到,因为 p 不是指针,所以没有使用虚拟表,而且因为它是一个人,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 指向一个 Woman,但事实并非如此。知道为什么吗?来自 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 的复制分配的实现,看看会发生什么:

//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

一切似乎都很好。

所以此时我不太明白作者的意思,所以如果有人能帮助我,我将不胜感激。

谢谢。

密件抄送。

最佳答案

Woman w(24, 10);

Person p = w;
p.Print();

24

Which was a bit of a surprise for me, being a noob, but then a realized that since p is not a pointer, the virtual table is not used, and since it's a Person, Person::Print() got called.

正确

So I knew that the copy constructor for Person got called, but I couldn't know if the copy constructor for Woman got called,...

不,它没有。

...but that wouldn't really matter, since p is a Person, and through it I'd never have access to Woman::Hotness, not even if I tried a cast.

考虑 Person p = 行创建了一个新变量 p,它具有足够的内存字节来存储 Person 的数据。如果您调用复制构造函数 Person::Person(const Person&); 代码只知道 Person 的数据成员 - 而不是任何派生类型的数据成员 - 所以“切片” Woman 对象复制构成 Person 的数据成员。没有地方放hotness,也没有被复制。


Person* p = new Person(20);
p->Print();
*p = w;
p->Print();

20
copy assignment
24

I thought the second call to p->Print() would call Woman::Print() since p was pointing to a Woman, but it didn't. Any idea why? The copy assignment from Person got called, I think because p is a Person*.

*p 指的是您刚刚分配的 Person 对象。 new 仅被告知有关 Person - 它无法知道您可能想要/期望/希望有额外的空间来放置 Woman 的额外字段 稍后可以复制,因此它只是为 Person 分配了空间。当您编写 *p = w; 时,它仅复制属于 Person 的字段,使用 Person::operator=(const Person&)功能。这不会设置指向虚拟调度表的指针来寻址Woman 的表...同样没有Woman 的知识...这就是为什么即使是 >像 Print 这样的虚拟 函数以后不会被解析为 Woman::Print


Person* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();

20 and 7
copy assignment
24 and 7

So I guess because p is Person* the copy assignment for Person got called, but not the one for Woman. Weirdly enough, the age got updated but the value of hotness remained the same, and I have no idea why.

这里,虽然 p 确实指向一个 Womanhotness 的额外数据成员,但复制仍然使用 完成Person::operator=,所以它不知道把额外的字段复制过来。有趣的是,它确实将内部指针复制到虚拟调度表,因此当您使用 p->Print() 时,它会调度到 Woman::Print


Woman* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();

20 and 7
copy assignment
copy assignment of woman
24 and 10

Now the numbers seem to be right.

是的,因为编译器知道分配和复制Woman 的所有数据成员,其中包括指向虚拟调度表和hotness 的指针。


您的其余实验(删除显式定义的赋值运算符)表明,哪些成员被复制以及虚拟调度表指针是否/如何更新的问题是涉及的静态类型的基础,因此这些问题是有或没有你的实现。


So at this point I can't quite understand what the author meant to say, so if anyone could help me out, I'd appreciate it.

他的意思是,如果有人认为他们正在获取一个指针或对 Person 的引用并照原样复制它(就像您之前的尝试一样),他们通常会不小心删除派生类(Woman) 相关成员并以一个简单的 Person 对象结束,在应用程序逻辑级别,Woman 是有意义的。通过删除这些运算符,编译器将防止这种意外的切片构造。正确的做法是提供一个 clone() 函数来创建一个新对象,无论动态对象类型是什么,允许一种“虚拟拷贝”。如果您搜索“克隆”,您会找到很多解释和示例。

关于C++ 为什么我应该抑制默认的复制构造函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22468067/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com