【问题标题】:Under what circumstances must I provide, assignment operator, copy constructor and destructor for my C++ class? [duplicate]在什么情况下我必须为我的 C++ 类提供赋值运算符、复制构造函数和析构函数? [复制]
【发布时间】:2011-01-24 01:56:26
【问题描述】:

假设我有一个类,其中唯一的数据成员类似于std::stringstd::vector。我需要提供复制构造函数、析构函数和赋值运算符吗?

【问题讨论】:

    标签: c++ destructor copy-constructor assignment-operator


    【解决方案1】:

    如果您的类仅包含向量/字符串对象作为其数据成员,则无需实现这些。 C++ STL 类(如向量、字符串)有自己的复制 ctor、重载赋值运算符和析构函数。

    但如果你的类在构造函数中动态分配内存,那么简单的浅拷贝会导致麻烦。在这种情况下,您必须实现复制 ctor、重载赋值运算符和析构函数。

    【讨论】:

      【解决方案2】:

      通常的经验法则是:如果您需要其中之一,那么您需要全部。

      不过,并非所有课程都需要它们。如果您的班级没有资源(尤其是内存),那么没有它们就可以了。例如,具有单个 stringvector 成分的类实际上并不需要它们 - 除非您需要一些特殊的复制行为(默认只会复制成员)。

      【讨论】:

      • 与其说“不是所有的类都需要它们”,不如说“保留默认的复制构造函数、析构函数和赋值运算符就可以了”更准确吗? (也就是说,您不需要使用自己的实现来覆盖默认值。)
      【解决方案3】:

      如果向量是按值声明的,则默认复制构造函数将复制向量。请注意,如果您在向量中存储了指针,在这种情况下,您需要为复制/分配/销毁提供特定的行为,以避免内存泄漏或多次删除。

      【讨论】:

        【解决方案4】:

        当您需要编写自己的三巨头时,我可以想到一些案例。所有标准容器都知道如何复制和销毁自己,因此您不一定需要编写它们。以下是如何知道您何时这样做:

        我的班级是否拥有任何资源?

        指针的默认复制语义是复制指针的,而不是它指向的内容。如果您需要深度复制某些内容,即使它存储在标准容器中,您也需要编写自己的复制构造函数和赋值运算符。您还需要编写自己的析构函数来正确释放这些资源。

        有人会继承我的班级吗?

        基类需要一个析构函数。 Herb Sutter 建议将它们设置为 publicvirtual(最常见的情况)或 protected 和非虚拟的,具体取决于您想对它们做什么。编译器生成的析构函数是公共的且非虚拟的,因此您必须编写自己的析构函数,即使其中没​​有任何代码。 (注意:这并不意味着您必须编写复制构造函数或赋值运算符。

        我应该阻止用户复制我的类的对象吗?

        如果您不希望用户复制您的对象(可能成本太高),您需要声明复制构造函数和赋值运算符protectedprivate。除非您需要它们,否则您不必实施它们。 (注意:这并不意味着您必须编写析构函数。

        底线:

        最重要的是了解编译器生成的复制构造函数、赋值运算符和析构函数会做什么。你不需要害怕他们,但你需要考虑他们并决定他们的行为是否适合你的班级。

        【讨论】:

          【解决方案5】:

          不,但有很多原因导致您不应该让编译器自动生成这些函数。

          根据我的经验,最好自己定义它们,并养成在更改课程时确保它们保持不变的习惯。首先,您可能希望在调用特定 ctor 或 dtor 时设置断点。不定义它们也可能导致代码膨胀,因为编译器将生成对成员 ctor 和 dtor 的内联调用(Scott Meyers 对此有一节)。

          此外,您有时还想禁止默认的复制 ctor 和分配。例如,我有一个存储和操作非常大的数据块的应用程序。我们通常拥有相当于一个包含数百万个 3D 点的 STL 向量,如果我们允许这些容器被复制构造,那将是一场灾难。因此,ctor 和赋值运算符被声明为私有且未定义。这样如果有人写

          class myClass {
            void doSomething(const bigDataContainer data); // not should be passed by reference
          }
          

          然后他们会得到一个编译器错误。我们的经验是显式的 become() 或 clone() 方法更不容易出错。

          所以总而言之,有很多理由避免自动生成编译器函数。

          【讨论】:

          • “养成在更换班级时确保它们得到维护的习惯”。这是不必要的维护噩梦。
          • 你不应该对你的ctors等进行单元测试来检查正确的初始化吗?您不应该考虑将数据成员添加到类的所有含义吗?如果您向一个类添加一个新字符串,对使用它的所有方法以及所有可能包含它的实例的类的代码膨胀有什么影响?添加新成员后,您是否不必重新考虑允许自动生成是否可行?虽然您想知道添加到 copy-ctor 和 op= 的所有这些东西是最小的。
          • 所以你还需要更新单元测试? (我真的没有想到我应该测试包含几个字符串的简单类的分配。) - 可能是代码膨胀,但在这种情况下,“优化大小”选项没有帮助?
          【解决方案6】:

          这些容器将需要一个“可复制构造”元素,如果您不提供复制构造函数,它将通过从您的类成员(浅拷贝)推导来调用您的类的默认复制构造函数。

          关于默认复制构造函数的简单解释在这里:http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html

          对于析构函数也是如此,如果你不提供一个容器需要访问你的析构函数或你的默认类析构函数(即,如果你声明你的析构函数是私有的,它将不起作用)

          【讨论】:

          • 发现提供的链接中的信息很有帮助。
          【解决方案7】:

          如果需要,您需要提供它们。或您的课程的可能用户。析构函数始终是必须,而复制构造函数和赋值运算符由编译器自动创建。 (至少是 MSVC)

          【讨论】:

          • 析构函数也是自动的(虽然编译器不会将它们设为虚拟,但这是另一个问题)。
          【解决方案8】:

          当你有一个需要深拷贝的类时,你应该定义它们。

          具体来说,任何包含指针或引用的类都应该包含它们,例如:

          class foo {
          private:
              int a,b;
              bar *c;
          }
          

          主观上,我想说总是定义它们,因为编译器生成的版本提供的默认行为可能不是您期望/想要的。

          【讨论】:

          • 也许最好说:如果类拥有资源。事实上,bar 实例c 指向可能在其他地方拥有和控制,而foo 只是对象的共享用户。 - 有趣的是,如果默认值可以,我还建议 not 定义它们:与编译器相比,您更容易犯错误并中断复制和分配(在析构函数中,您没有什么可以做的)在这种情况下首先要做)。
          • @visitor:见 lilburne 的回答 - 基本相同,但原因更详细 - 主观上,我觉得他的钱是对的。
          • 如果您想要除浅层的、按成员的副本之外的任何内容,您当然需要它们。但我不完全相信为什么你应该手动进行成员复制(这对我来说是大多数类,如果它们首先是可复制的) - 如果这不是你所期望的,也许你期望复制会产生非常奇怪的语义。 - 也许手动编写赋值运算符的一个客观原因是您可以提供更强大的异常保证(lhv 没有改变,不仅仅是没有内存泄漏),但我认为这将是非常棘手的(需要回滚更改)要普遍完成。
          【解决方案9】:

          不适用于字符串或向量,因为微不足道的构造函数/析构函数等都可以。

          如果您的类有指向其他数据的指针并且需要深拷贝,或者如果您的类拥有必须释放或必须以特殊方式复制的资源。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-01-04
            • 2011-07-19
            • 1970-01-01
            • 2011-05-19
            • 1970-01-01
            • 1970-01-01
            • 2011-06-09
            相关资源
            最近更新 更多