C++为什么要压缩默认的复制构造函数?

Bru*_*tos 5 c++ pointers copy-constructor

来自Bjarne Stroustrup的The C++ Programming Language第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();
        // ...
};
Run Code Online (Sandbox Code Playgroud)

为了试图理解他的意思,我创建了以下示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

该版本程序的输出是:

copy constructor
24
Run Code Online (Sandbox Code Playgroud)

这对我来说有点意外,作为一个菜鸟,但随后意识到由于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;
}
Run Code Online (Sandbox Code Playgroud)

新的输出是:

20
24 and 10
Run Code Online (Sandbox Code Playgroud)

现在p是一个指针,因为它是一个指针,没有复制或移动,只需更改引用.

然后我想我可以尝试取消引用p并为其分配w:

int main() {
    Woman w(24, 10);

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出是这样的:

20
copy assignment
24
Run Code Online (Sandbox Code Playgroud)

我认为第二次调用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;
}
Run Code Online (Sandbox Code Playgroud)

新的输出是这样的:

20 and 7
copy assignment
24 and 7
Run Code Online (Sandbox Code Playgroud)

所以我猜是因为p是Person*,被调用的人的副本分配,但不是女人的副本.很奇怪,年龄得到了更新,但热度的价值保持不变,我不明白为什么.

再试一次:

int main() {
    Woman w(24, 10);

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

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

20 and 7
copy assignment
copy assignment of woman
24 and 10
Run Code Online (Sandbox Code Playgroud)

现在数字似乎是正确的.

我的下一步是删除Person的副本分配的实现,并查看是否会调用默认值:

//Person& operator=(const Person& from) { cout << "copy assignment" << endl; age = from.Age(); return *this; }
Run Code Online (Sandbox Code Playgroud)

输出:

20 and 7
copy assignment of woman
24 and 10
Run Code Online (Sandbox Code Playgroud)

请注意,年龄被复制,所以不用担心.

下一个明显的举措是删除女性副本分配的实现,看看会发生什么:

//Woman& operator=(const Woman& from) { Person::operator=(from); cout << "copy assignment of woman" << endl; hotness = from.Hotness(); return *this; };
Run Code Online (Sandbox Code Playgroud)

输出:

20 and 7
24 and 10
Run Code Online (Sandbox Code Playgroud)

一切似乎都很好.

所以在这一点上我无法理解作者的意思,所以如果有人能帮助我,我会很感激.

谢谢.

嗜铬细胞.

Ton*_*roy 6

Woman w(24, 10);

Person p = w;
p.Print();
Run Code Online (Sandbox Code Playgroud)

24

这对我来说有点意外,作为一个菜鸟,但随后意识到由于p不是指针,因此不使用虚拟表,因为它是Person,所以调用了Person :: Print().

正确

所以我知道Person的拷贝构造函数被调用了,但我不知道是否调用了Woman的拷贝构造函数,...

不,它没有.

......但这并不重要,因为p是一个人,通过它,我永远无法访问Woman :: Hotness,即使我尝试演员也没有.

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


Person* p = new Person(20);
p->Print();
*p = w;
p->Print();
Run Code Online (Sandbox Code Playgroud)

20
份副本
24

我认为第二次调用p-> Print()会调用Woman :: Print(),因为p指的是一个女人,但事实并非如此.知道为什么吗?来自Person的副本分配被调用,我认为因为p是Person*.

*p指的是Person您刚刚分配的对象.将new只告知Person-它无法知道你可能想/希望的方式/希望换入的附加字段额外的空间Woman,可以在以后复制,所以它只是分配的空间了Person.当您编写*p = w;它时Person,使用该Person::operator=(const Person&)函数仅复制属于a的字段.这不会将指向虚拟调度表的指针设置为地址Woman的表...再次不知道Woman...这就是为什么即使是virtual类似的函数Print也不会在Woman::Print以后解决.


Person* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();
Run Code Online (Sandbox Code Playgroud)

20和7
复制分配
24和7

所以我想因为pPerson*被调用的人的副本分配,而不是女人的副本分配.很奇怪,年龄得到了更新,但热度的价值保持不变,我不明白为什么.

在这里,虽然p指向Woman带有额外数据成员的a hotness,但副本仍然使用Person::operator=,因此它不知道复制额外的字段.有趣的是,它确实将内部指针复制到虚拟调度表,因此当您使用p->Print()它调度时Woman::Print.


Woman* p = new Woman(20, 7);
p->Print();
*p = w;
p->Print();
Run Code Online (Sandbox Code Playgroud)

妇女 24和10的
复印件分配
复印件分别为20和7

现在数字似乎是正确的.

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


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


所以在这一点上我无法理解作者的意思,所以如果有人能帮助我,我会很感激.

他所说的是,如果有人认为他们正在获取指针或对a的引用Person并将其复制(如在之前的尝试中),则他们经常意外地删除派生的class(Woman)相关成员并最终得到一个简单的Person对象在应用程序逻辑级别,a Woman将有意义.通过删除这些运算符,编译器将防止这种意外切片构造.正确的做法是提供一个clone()函数来创建一个动态对象类型的新对象,允许一种"虚拟副本".如果你搜索"克隆",你会发现很多解释和例子.