关于C++:copy复制对象

What is The Rule of Three?

  • 复制对象是什么意思?
  • 什么是复制构造函数和复制分配运算符?
  • 我什么时候需要自己申报?
  • 如何防止复制对象?

  • 在投票结束之前,请阅读整个主题和c++-faq标签wiki。
  • @二进制:至少在投票前花点时间阅读评论讨论。文本过去简单得多,但弗雷德被要求对其进行扩展。另外,虽然从语法上讲这是四个问题,但它实际上只是一个有几个方面的问题。(如果您不同意这一点,那么通过单独回答这些问题来证明您的POV,并让我们对结果进行投票。)
  • 弗莱德,这里有一个有趣的关于C++ 1x的回答:StAdppOrth.COM/Quase/4782757/& Helip;我们该怎么处理?
  • 相关:大二定律
  • 请记住,对于C++ 11,我认为这已经升级到五的规则,或者类似的东西。
  • @paxdiablo零规则是精确的。


介绍

C++用数值语义处理用户定义类型的变量。这意味着对象在不同的上下文中被隐式复制,我们应该理解"复制一个对象"的真正含义。好的。

让我们考虑一个简单的例子:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(如果你对name(name), age(age)部分感到困惑,这称为成员初始值设定项列表。)好的。特殊成员函数

复制person对象意味着什么?main函数显示了两种不同的复制场景。初始化person b(a);由复制构造函数执行。它的工作是基于现有对象的状态构造一个新对象。分配由拷贝分配操作员执行。它的工作通常要复杂一点,因为目标对象已经处于某个需要处理的有效状态。好的。

因为我们自己既没有声明复制构造函数也没有声明赋值运算符(也没有声明析构函数),这些都是为我们隐式定义的。引用标准:好的。

The [...] copy constructor and copy assignment operator, [...] and destructor are special member functions.
[ Note: The implementation will implicitly declare these member functions
for some class types when the program does not explicitly declare them.
The implementation will implicitly define them if they are used. [...] end note ]
[n3126.pdf section 12 §1]

Ok.

默认情况下,复制对象意味着复制其成员:好的。

The implicitly-defined copy constructor for a non-union class X performs a memberwise copy of its subobjects.
[n3126.pdf section 12.8 §16]

Ok.

The implicitly-defined copy assignment operator for a non-union class X performs memberwise copy assignment
of its subobjects.
[n3126.pdf section 12.8 §30]

Ok.

隐式定义

person隐式定义的特殊成员函数如下:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}

// 2. copy assignment operator
person& operator=(const person& that)
{
    name = that.name;
    age = that.age;
    return *this;
}

// 3. destructor
~person()
{
}

在这种情况下,memberwise复制正是我们想要的:nameage是复制的,因此我们得到一个独立的person对象。隐式定义的析构函数始终为空。在这种情况下,这也很好,因为我们没有在构造函数中获得任何资源。成员的析构函数在person析构函数完成后隐式调用:好的。

After executing the body of the destructor and destroying any automatic objects allocated within the body,
a destructor for class X calls the destructors for X's direct [...] members
[n3126.pdf 12.4 §6]

Ok.

管理资源

那么,我们什么时候应该显式声明这些特殊成员函数呢?当我们班管理一个资源时,也就是说,当类的对象负责该资源时。这通常意味着资源是在构造函数中获得的(或传递给构造函数)并在析构函数中释放。好的。

让我们及时回到标准的C++。没有像std::string这样的东西,程序员喜欢指针。person类可能是这样的:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

即使在今天,人们仍然以这种方式编写课程并陷入困境:"我把一个人推到一个向量上,现在我出现了疯狂的记忆错误!"记住,默认情况下,复制对象意味着复制其成员,但是复制name成员只复制一个指针,而不是它指向的字符数组!这有几个不愉快的影响:好的。

  • 通过a的变化可以通过b观察到。
  • 一旦b被销毁,a.name就是一个悬空指针。
  • 如果a被破坏,删除悬空指针将产生未定义的行为。
  • 由于转让没有考虑到name在转让前所指的内容,迟早你会发现到处都是记忆泄漏。
  • 明确的定义

    由于memberwise复制没有所需的效果,因此必须显式定义复制构造函数和复制分配运算符,以便对字符数组进行深度复制:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 1. copy constructor
    person(const person& that)
    {
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }

    // 2. copy assignment operator
    person& operator=(const person& that)
    {
        if (this != &that)
        {
            delete[] name;
            // This is a dangerous point in the flow of execution!
            // We have temporarily invalidated the class invariants,
            // and the next statement might throw an exception,
            // leaving the object in an invalid state :(
            name = new char[strlen(that.name) + 1];
            strcpy(name, that.name);
            age = that.age;
        }
        return *this;
    }

    注意初始化和赋值之间的区别:在分配给name以防止内存泄漏之前,必须删除旧状态。此外,我们还必须防止表格x = x自行转让。如果不进行检查,delete[] name将删除包含源字符串的数组,因为在编写x = x时,this->namethat.name都包含相同的指针。好的。例外安全

    不幸的是,如果new char[...]由于内存耗尽而抛出异常,则此解决方案将失败。一种可能的解决方案是引入局部变量并对语句重新排序:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 2. copy assignment operator
    person& operator=(const person& that)
    {
        char* local_name = new char[strlen(that.name) + 1];
        // If the above statement throws,
        // the object is still in the same state as before.
        // None of the following statements will throw an exception :)
        strcpy(local_name, that.name);
        delete[] name;
        name = local_name;
        age = that.age;
        return *this;
    }

    这还可以在不进行明确检查的情况下处理自我分配。对于这个问题,一个更强大的解决方案是复制和交换习惯用法,但我不会在这里详细讨论异常安全。我只提到了例外情况来说明以下几点:编写管理资源的类是困难的。好的。不可复制资源

    某些资源不能或不应该被复制,例如文件句柄或互斥体。在这种情况下,只需声明copy constructor和copy assignment operator为private,而不给出定义:好的。

    1
    2
    3
    4
    private:

        person(const person& that);
        person& operator=(const person& that);

    或者,您可以从EDCOX1 OR 14继承或声明为删除(在C++ 11和以上):好的。

    1
    2
    person(const person& that) = delete;
    person& operator=(const person& that) = delete;

    三法则

    有时需要实现一个管理资源的类。(不要在一个类中管理多个资源,这只会导致疼痛。)在这种情况下,记住三条规则:好的。

    If you need to explicitly declare either the destructor,
    copy constructor or copy assignment operator yourself,
    you probably need to explicitly declare all three of them.

    Ok.

    (不幸的是,这个"规则"不是由C++标准或任何我意识到的编译器强制执行的。好的。五法则

    从C++ 11开始,对象有2个额外的特殊成员函数:移动构造函数和移动赋值。实施这些职能的五个国家的规则。好的。

    带有签名的示例:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class person
    {
        std::string name;
        int age;

    public:
        person(const std::string& name, int age);        // Ctor
        person(const person &) = default;                // Copy Ctor
        person(person &&) noexcept = default;            // Move Ctor
        person& operator=(const person &) = default;     // Copy Assignment
        person& operator=(person &&) noexcept = default; // Move Assignment
        ~person() noexcept = default;                    // Dtor
    };

    零的法则

    3/5规则也被称为0/3/5规则。规则的零部分说明在创建类时,不允许编写任何特殊成员函数。好的。忠告

    大多数时候,你不需要自己管理资源,因为像std::string这样的现有类已经为您做了。只需使用std::string成员比较简单代码对于使用char*的复杂且容易出错的替代方案,您应该深信不疑。只要您远离原始指针成员,三规则就不太可能涉及您自己的代码。好的。好啊。

    • 我将"define"改为"define或declare private",因为这里有类(资源句柄、互斥体…怪物?)不存在"复制"的语义。那么,仅使用析构函数就足够了,复制构造函数和operator=都需要简单地声明为私有,以避免意外复制。
    • 弗雷德,如果(a)你不会在可复制代码中拼写出错误实现的分配,并添加一个说明,说明它是错误的,并在精装本中查找其他地方,或者在代码中使用C&S,或者跳过实现所有这些成员,我会对我的投票结果感觉更好(b)你会缩短前半部分,这与腐烂无关;(c)你会我们将讨论移动语义学的引入以及这对rot意味着什么。
    • SBI:这个问题被标记为C++FAQ,所以高度评价的成员如你自己应该自由地修正原来的帖子(如果它包含误导性信息)通过编辑它而不是仅仅给出一个评论,要求作者更正他的帖子。-)
    • 不过,我想,这样的话就应该用信用证了。我喜欢你保持术语的大部分准确性(也就是说你说的是"复制分配运算符",并且你没有进入一个常见的陷阱,即分配并不意味着复制)。
    • 伟大的职位。你能提供一个参考点,你在那里引用的标准吗?以防有人想查。
    • @我不认为删掉一半的答案会被视为非CW答案的"公平编辑"。
    • 另外,我可能读过头了,但你没有提到,在做任何事情之前,副本分配操作员应该检查身份。
    • @太空人:其实不需要。复制和交换处理这个问题。这就是为什么这应该使用C&S或者不使用任何实现,而只是参考GMAN的C&S文章。但似乎弗雷德一发表他的答案就藏起来了。:)
    • (不幸的是,这个"规则"不是由C++标准或任何我意识到的编译器执行的)。这个语句被短语所击败:你可能需要显式声明所有这三个词。可能不是规则。
    • 类处理资源,原始指针不是唯一这样的资源。一般来说,当您编写一个处理资源的类时,除非您正在编写系统,否则将使其不可复制。(例如,如果您出于任何原因正在实现智能指针)。
    • 如果你更新了C++ 11的文章(即移动构造函数/赋值),那就太好了。
    • 基本上,硬编码到std::vector中的绑定检查可能会严重损害您的性能。这就是为什么你想使用指针的唯一原因,但是这是一个非常有效的原因,因为生成快速代码是你首先想要使用繁琐、冗长、模糊和模糊的语言(C++)的唯一原因。在这一点上,您可能需要所有与C++ 11一起引入的就地分配CRAP,这将迫使您小便100行费力的"规范"小程序,以确保您的代码不会将垃圾吐到内存中并崩溃。
    • @Kuroineko std::vector不检查边界(除非使用at,但为什么使用它而不是operator[]
    • @Etiennedelatel operator[]经常在调试模式下进行边界检查,这会导致性能降低;这正是调试模式的目的。如果有人抱怨调试模式更慢、更安全,那么就让他们看看去精神病院的方法。
    • @实际上,Visual Studio是这样做的。对于语言纯洁性的守护者们之间的争论,我不会三思而后行,这只是我在VS2013生成的(发行版)汇编代码中亲眼所见。除非你真的需要快速代码,并且准备好经历1970个小的实现问题,并且在处理了最新标准演进所引入的抽象语言结构之后,才停止C++的另一个原因。
    • @Kuroineko我从未见过Visual Studio在发布模式下执行边界检查。不管怎样,你总是可以写T * p = v.data();T * p = &v[0];,并与你心爱的指针一起工作。
    • 我不喜欢指针。我只是说,使用普通向量会花费大量的CPU,这取决于您使用的编译器,甚至是STL版本。至于你的解决方案,它践踏了许多好的实践规则,以至于你需要一头公牛来埋葬那堆死猫咪——如果你调整向量的大小,它可能会崩溃,或者效率低下,因为addres必须重新读取。至于vs所做的,它本身并不是绑定检查。它试图"整理"这个向量,以防有人改变它的大小。如果你感兴趣的话,这是在vectorstl include的1621行。
    • 你能澄清/限定这一说法吗(或为我指出正确的解释方向):"永远不要在一个类中管理多个资源,这只会导致痛苦。"在我看来,OOP的一个主要优势是将(通常是多个)资源封装在一个类中,这样该类的客户就可以不用担心这些资源的管理。
    • @wickstopher的意思是,例如,您不应该有两个char*数据成员(以及一个复制构造函数、一个赋值运算符和一个析构函数,每个成员都管理char*在类A中的两个char*数据成员,而是应该编写另一个类B,其中一个char*数据成员(以及所有复杂的逻辑),然后有两个B类中的数据成员A类。那么您根本不需要类A中的复杂逻辑。当然,您不应该编写和使用类B,而应该只在类A内使用std::string,这有意义吗?
    • 这可能是一个愚蠢的问题,但当你谈论"资源"时,你到底指的是什么?
    • @使用后必须释放的任何内容:并发锁、文件句柄、数据库连接、网络套接字、堆内存…
    • 如果我运行person b=a;语句,会调用复制构造函数,还是会调用复制赋值运算符?


    三的规则是C++的经验法则,基本上说

    If your class needs any of

    • a copy constructor,
    • an assignment operator,
    • or a destructor,

    defined explictly, then it is likely to need all three of them.

    这样做的原因是,这三个类通常都用于管理一个资源,如果您的类管理一个资源,那么它通常需要管理复制和释放。

    如果没有良好的语义来复制类管理的资源,那么考虑通过将复制构造函数和赋值运算符声明(不定义)为private来禁止复制。

    (注意,即将到来的新版本的C++标准(即C++ 11)将移动语义添加到C++中,这将有可能改变三的规则。然而,我对编写一个关于三的规则的C++ 11节知之甚少。

    • 防止复制的另一个解决方案是从无法复制的类继承(私有)(如boost::noncopyable)。它也可以更加清晰。我认为C++0x和"删除"函数的可能性在这里可以帮助,但是忘记了语法:
    • @马修:是的,那也行。但是,除非noncopyable是std lib的一部分,否则我不认为它有很大的改进。(哦,如果你忘了删除语法,你就忘了我认识的莫伊桑。:)
    • 关于三和C++ 1x规则的任何更新?
    • @大安:看看这个答案。不过,我建议你坚持马蒂尼奥的零定律。对我来说,这是C++在过去十年中最重要的经验法则之一。
    • Martinho的零规则现在更好了(没有明显的广告软件接管),位于archive.org上。


    大三定律如上所述。

    一个简单的例子,用简单的英语,它解决的问题是:

    非默认析构函数

    您在构造函数中分配了内存,因此需要编写一个析构函数来删除它。否则会导致内存泄漏。

    你可能认为这是工作完成了。

    问题是,如果复制对象,那么复制对象将指向与原始对象相同的内存。

    一旦其中一个删除了它的析构函数中的内存,另一个将有一个指向无效内存的指针(这称为悬空指针),当它试图使用它时,事情将变得棘手。

    因此,您可以编写一个复制构造函数,以便它为新对象分配自己的内存片段来销毁。

    赋值运算符和复制构造函数

    您已将构造函数中的内存分配给类的成员指针。复制此类的对象时,默认的赋值运算符和复制构造函数会将此成员指针的值复制到新对象。

    这意味着新对象和旧对象将指向同一内存块,因此当您在一个对象中更改它时,另一个对象也将更改它。如果一个对象删除了这个内存,另一个将继续尝试使用它-eek。

    要解决这个问题,您需要编写自己版本的复制构造函数和赋值运算符。您的版本为新对象分配单独的内存,并跨第一个指针指向的值(而不是其地址)进行复制。

    • 因此,如果我们使用复制构造函数,那么复制是在完全不同的内存位置进行的;如果我们不使用复制构造函数,那么复制是在相同的内存位置进行的,但它指向相同的内存位置。这就是你想说的吗?因此,没有复制构造函数的复制意味着一个新的指针将在那里,但指向相同的内存位置。但是,如果我们有用户显式定义的复制构造函数,那么我们将有一个单独的指针指向不同的内存位置,但具有数据。
    • 对不起,我早就回答过这个问题了,但我的回答似乎不在这里:(基本上,是的,你明白了。)
    • 原则如何适用于复制分配运算符?如果提到第三条规则中的第三条,这个答案会更有用。
    • @dbedrenko,"您编写一个复制构造函数,以便它为新对象分配自己的内存……"这是扩展到复制分配运算符的相同原则。你认为我没有说清楚吗?
    • @斯特凡谢谢你的回答,但我不清楚,最好把它说清楚。我来这个问题是为了学习复制分配操作符的用途,所以最好明确地了解它解决了什么问题。
    • @dbedrenko,我添加了更多信息。这会更清楚吗?


    基本上,如果您有一个析构函数(不是默认的析构函数),这意味着您定义的类有一些内存分配。假设类在外部被一些客户机代码或您使用。

    1
    2
    3
        MyClass x(a, b);
        MyClass y(c, d);
        x = y; // This is a shallow copy if assignment operator is not provided

    如果MyClass只有一些基元类型的成员,则默认的赋值运算符可以工作,但如果它有一些指针成员和没有赋值运算符的对象,则结果将是不可预测的。因此,我们可以说,如果类的析构函数中有要删除的内容,我们可能需要一个深度复制操作符,这意味着我们应该提供一个复制构造函数和赋值操作符。


    复制对象是什么意思?有几种方法可以复制对象——让我们来谈谈您最可能提到的两种类型——深度复制和浅层复制。

    因为我们使用的是面向对象的语言(或者至少假设是这样),所以假设您已经分配了一段内存。由于它是一种OO语言,我们可以很容易地引用我们分配的内存块,因为它们通常是由我们自己的类型和原语组成的原语变量(ints、chars、bytes)或我们定义的类。因此,假设我们有一类汽车,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    class Car //A very simple class just to demonstrate what these definitions mean.
    //It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
    {
    private String sPrintColor;
    private String sModel;
    private String sMake;

    public changePaint(String newColor)
    {
       this.sPrintColor = newColor;
    }

    public Car(String model, String make, String color) //Constructor
    {
       this.sPrintColor = color;
       this.sModel = model;
       this.sMake = make;
    }

    public ~Car() //Destructor
    {
    //Because we did not create any custom types, we aren't adding more code.
    //Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
    //Since we did not use anything but strings, we have nothing additional to handle.
    //The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
    }

    public Car(const Car &other) // Copy Constructor
    {
       this.sPrintColor = other.sPrintColor;
       this.sModel = other.sModel;
       this.sMake = other.sMake;
    }
    public Car &operator =(const Car &other) // Assignment Operator
    {
       if(this != &other)
       {
          this.sPrintColor = other.sPrintColor;
          this.sModel = other.sModel;
          this.sMake = other.sMake;
       }
       return *this;
    }

    }

    深度复制是指如果我们声明一个对象,然后创建一个完全独立的对象副本…我们最终在两个完全相同的内存集中创建了两个对象。

    1
    2
    3
    4
    Car car1 = new Car("mustang","ford","red");
    Car car2 = car1; //Call the copy constructor
    car2.changePaint("green");
    //car2 is now green but car1 is still red.

    现在让我们做些奇怪的事情。假设car2的编程错误,或者有意共享car1的实际内存。(这通常是一个错误,在课堂上,这通常是它下面讨论的毯子。)假设每当你问到car2时,你真的正在解决指向car1内存空间的指针……这或多或少是一个肤浅的副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //Shallow copy example
    //Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
    //Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.

     Car car1 = new Car("ford","mustang","red");
     Car car2 = car1;
     car2.changePaint("green");//car1 is also now green
     delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve
     the address of where car2 exists and delete the memory...which is also
     the memory associated with your car.*/

     car1.changePaint("red");/*program will likely crash because this area is
     no longer allocated to the program.*/

    因此,不管你用什么语言写作,在复制对象时要非常小心你的意思,因为大多数时候你想要一个深度的复制。

    什么是复制构造函数和复制分配运算符?我已经在上面用过了。当您键入诸如Car car2 = car1;之类的代码时,实际上如果您声明一个变量并将其赋给一行,则调用复制构造函数,即调用复制构造函数时。赋值运算符是使用等号时所发生的事情——car2 = car1;。注意,car2没有在同一报表中声明。为这些操作编写的两段代码可能非常相似。事实上,典型的设计模式有另一个函数,当您满意初始复制/分配是合法的时,您可以调用它来设置所有内容——如果您查看我编写的长手代码,这些函数几乎是相同的。

    我什么时候需要自己申报?如果您不编写要共享或以某种方式用于生产的代码,那么您只需要在需要时声明它们。如果您选择"意外地"使用程序语言,并且没有使用它,那么您就需要知道程序语言会做什么——也就是说,您得到了编译器的默认值。例如,我很少使用复制构造函数,但赋值运算符重写是非常常见的。你知道你可以改写加减法的意思吗?

    如何防止复制对象?用私有函数覆盖所有允许为对象分配内存的方法是一个合理的开始。如果您真的不想让人们复制它们,您可以将其公开,并通过抛出异常并不复制对象来警告程序员。

    • 这个问题是用C++来标记的。这种伪代码的说明充其量并不能澄清关于定义良好的"三法则"的任何事情,而且最坏的情况下只会传播混乱。


    When do I need to declare them myself?

    三条规则规定,如果你申报

  • 复制构造函数
  • 复制分配运算符
  • 析构函数
  • 那么你应该把这三个都申报出来。它产生于这样的观察,即接管复制操作的意义的需要几乎总是源于执行某种资源管理的类,这几乎总是意味着

    • 在一个复制操作中进行的任何资源管理都可能需要在另一个复制操作中进行,并且

    • 类析构函数也将参与资源的管理(通常释放它)。要管理的经典资源是内存,这就是为什么所有标准库类管理内存(例如,执行动态内存管理的STL容器)都声明"三大":复制操作和析构函数。

    第三条规则的一个结果是,用户声明的析构函数的存在表明简单的成员级复制不太可能适用于类中的复制操作。反过来,这表明,如果一个类声明了一个析构函数,那么复制操作可能不应该自动生成,因为它们不会做正确的事情。在采用C++ 98时,这条推理线的意义没有得到充分的理解,因此在C++ 98中,用户声明的析构函数的存在对编译器生成复制操作的意愿没有影响。在C++ 11中,情况仍然如此,但仅仅因为限制生成复制操作的条件会破坏太多的遗留代码。

    How can I prevent my objects from being copied?

    将复制构造函数和复制分配运算符声明为私有访问说明符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class MemoryBlock
    {
    public:

    //code here

    private:
    MemoryBlock(const MemoryBlock& other)
    {
       cout<<"copy constructor"<<endl;
    }

    // Copy assignment operator.
    MemoryBlock& operator=(const MemoryBlock& other)
    {
     return *this;
    }
    };

    int main()
    {
       MemoryBlock a;
       MemoryBlock b(a);
    }

    在C++ 11中,您还可以声明复制构造函数和赋值操作符被删除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MemoryBlock
    {
    public:
    MemoryBlock(const MemoryBlock& other) = delete

    // Copy assignment operator.
    MemoryBlock& operator=(const MemoryBlock& other) =delete
    };


    int main()
    {
       MemoryBlock a;
       MemoryBlock b(a);
    }


    许多现有的答案已经接触到复制构造函数、赋值运算符和析构函数。然而,在后C++ 11中,移动语义的引入可能会扩展到3以上。

    最近,Michael Claisse做了一个关于这个主题的演讲:http://channel9.msdn.com/events/c pp/c-pp-con-2014/the-canonical-class


    C++中的三条规则是设计的基本原则和三个要求的发展,如果在下列成员函数中有一个明确的定义,那么程序员应该一起定义其他两个成员函数。也就是说,以下三个成员函数是必不可少的:析构函数、复制构造函数、复制赋值运算符。

    C++中的复制构造函数是一种特殊的构造函数。它用于构建一个新对象,这个新对象相当于一个现有对象的副本。

    复制分配运算符是一种特殊的分配运算符,通常用于将现有对象指定给同一类型的其他对象。

    有一些简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // default constructor
    My_Class a;

    // copy constructor
    My_Class b(a);

    // copy constructor
    My_Class c = a;

    // copy assignment operator
    b = a;

    • 嗨,你的回答没有增加任何新内容。其他人更深入、更准确地涵盖了这个主题——你的答案是近似的,实际上在某些地方是错误的(也就是说,这里没有"必须"这个词,而是"很可能应该")。当你把这类问题的答案贴到已经被彻底回答的问题上时,这真的不值得。除非你有新的东西要补充。
    • 另外,有四个快速的例子,它们在某种程度上与三个规则所讨论的三个规则中的两个有关。太混乱了。