【问题标题】:How often do you implement the big three?您多久实施一次三巨头?
【发布时间】:2026-02-18 05:10:01
【问题描述】:

我只是在思考这里的问题数量,要么是关于“三大”(复制构造函数、赋值运算符和析构函数),要么是由于它们没有正确实现而导致的问题,当我想到我可以不记得我上次自己实现它们是什么时候了。对我最活跃的两个项目的快速 grep 表明,我仅在大约 150 个类中的一个类中实现了所有三个。

这并不是说我没有实现/声明其中的一个或多个 - 显然基类需要一个虚拟析构函数,并且我的大量类禁止使用私有复制 ctor 和赋值操作成语进行复制。但是完全实现了,有一个单独的类,它会做一些引用计数。

所以我想知道我在这方面是否不寻常?您多久实现一次所有这三个功能?您实现它们的类有什么模式吗?

【问题讨论】:

  • 读到这个问题让我很高兴我不用写C++了! (当然,这大概解释了为什么我很难修改别人的 C++。)
  • -1 @Norman 无需反对 C++。如果您不必编写 C++ 代码,请转到其他问题。我从来不理解人们因为他们不使用某些东西,所以他们想要比没有人使用它,反之亦然。
  • @Norman 我会认为它(和答案)是令人鼓舞的 - 你很少需要做一个复杂的任务。
  • @Norman 有些人可以在需要时使用几种不同的编程语言,使用几种不同的范式流利地编程。如果你做不到,那么说你不习惯的语言是没有价值的。
  • @Norman 好的,我会的。 C++ 是一种允许你喜欢的语言出现的语言。

标签: c++


【解决方案1】:

我认为您很少需要全部三个。大多数需要显式析构函数的类并不适合复制。

与大型显式析构函数相比,使用自毁成员(通常不需要复制构造之类的东西)只是更好的设计。

【讨论】:

  • pImpl 成语是一个常用的反例。
  • 即使您决定不想要 std::auto_ptr 的行为,您也只需为自己编写一个 pimpl_ptr 类,并在所有 PIMPL 类中使用该行为。现在你有 X 个使用 PIMPL 的类不需要它,还有 1 个类处理你的个人 pimpl_ptr 的行为,导致你的大多数类不需要它。
【解决方案2】:

我很少实现它们,但经常将它们声明为私有(即复制构造函数和赋值运算符)。

【讨论】:

    【解决方案3】:

    像你一样,几乎从来没有。

    但我并不依赖于 STL 编程方法,在这种方法中,您可以在容器中复制所有内容 - 通常如果它不是原始的,我会使用指针,无论是智能的还是其他的。

    我主要使用 RAII 模式,因此避免编写析构函数。不过,我确实在我的 .cc 文件中放置了空主体,以帮助保持代码膨胀。

    并且,像你一样,我将它们声明为私有且未实现,以防止任何意外调用。

    【讨论】:

    • 嗯?析构函数是 RAII 的基石。还是您的意思是您专门使用其他人编写的 RAII 类?
    • 空函数体如果在头文件中会导致更少的代码膨胀,因此可以内联它们。但我无法想象为什么要定义一个空的析构函数。
    • @Ben Voigt,显然是的,析构函数是 raii 的基础......但我通常不需要编写自己的 RAII 类,当我使用 Ravi 成员时,我不需要在析构函数中清理它们。
    • @potatoswatter:空的构造函数可能有很多编译器生成的代码、成员变量的构造和基类的初始化。这并不总是内联的好选择。析构函数也是如此。如果将它们放在头文件中,则该代码将在包含它的所有文件中生成。
    • 你最后一个斜体句子的相关性是什么?还是某种信号?
    【解决方案4】:

    这实际上取决于您正在处理的问题类型。过去几个月我一直在做一个新项目,我认为每个类都继承自 boost::noncopyable。九个月前,我参与了一个使用 POD 的不同项目,并且我利用了自动复制 ctor 和赋值运算符。如果您正在使用 boost::shared_ptr (并且您应该使用),那么现在编写自己的复制 ctor 或赋值运算符应该很少见。

    【讨论】:

      【解决方案5】:

      大多数时候,几乎没有。这是因为使用的成员(基于引用的智能 ptr 等)已经实现了正确的语义,或者对象是不可复制的。

      当我发现自己在实现这些时会出现一些模式:

      1. 破坏性复制,即像 auto_ptr 或 lock 这样的移动模式
      2. 在 C++ 中几乎没有出现过的 dispose 模式,但我在我的职业生涯中使用过它大约 3 次(实际上就在一周前)
      3. pimpl 模式,其中 pimpl 在标头中声明为 fwd,并由智能 ptr 管理。然后空的 dtor 进入 .cc 文件,但仍归类为“未生成编译器”

      当我认为我可能在某个地方有一个循环引用并且只是想确定时,另一个打印“我被摧毁了”的微不足道的。

      【讨论】:

        【解决方案6】:

        任何拥有一些指针成员的类都需要定义这三个操作来实现深度复制(请参阅here了解深度描述)。

        【讨论】:

        • 这不是拥有指针的问题,而是拥有它们所指向的东西的问题。一个类可以包含没有所有权的指针,在这种情况下可能不需要三巨头。而且我非常反对“深拷贝”这个词——只有一种拷贝操作——正确的。
        • @Neil Butterworth:您之前曾评论过我的回答,即“深拷贝”一词没有多大用处。 (见这里:*.com/questions/2657810/2657824#2657824)在另一个线程中,您没有明确说“只有一种复制操作 - 正确的”。我很好奇:那会是哪个?在特定情况下是正确的,还是通用的解决方案?
        • @Neil:理论上,如果你有一个带有shared_ptr 的类来表示可变的东西,那么“深拷贝”和“浅拷贝”是不同的操作,它们可能都适合不同的用户。其中只有一个可以是复制 ctor。可能是浅拷贝,深拷贝和深赋值可用作成员函数,但如果不同用户都需要它们,我认为这不是“正确”的问题。当然,我同意如果存在深度的问题(即,可变事物本身是否具有某种指针),该语言可能仍然不够。
        • @Neil 我认为你在玩文字游戏。当我说拥有一个指针时,我希望你能理解我。我也希望你同意我的观点,当一个类对其他类拥有所有权时,必须定义这三个操作。这正是问题所在。
        • @Neil 你可以随心所欲。 “深拷贝”一词存在且含义明确。