【问题标题】:What are the advantages of boost::noncopyableboost::noncopyable 的优点是什么
【发布时间】:2016-08-31 16:03:58
【问题描述】:

为了防止复制一个类,你可以很容易地声明一个私有的复制构造函数/赋值操作符。但是你也可以继承boost::noncopyable

在这种情况下使用 boost 有什么优点/缺点?

【问题讨论】:

  • 请注意,在 C++11 中你会写成 struct Foo{Foo(const Foo&)=delete;};
  • 我认为这主要是因为普通苦工不明白为什么你的复制构造函数是私有的和未定义的。
  • @spraff 我相信你还需要Foo & operator=(const Foo &) = delete;?
  • 是的。这是一个示例,而不是完整的实现。

标签: c++ boost noncopyable


【解决方案1】:

我没有看到任何文档优势:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

对比:

struct A
{
     A(const A&) = delete;
     A& operator=(const A&) = delete;
};

当您添加仅移动类型时,我什至认为文档具有误导性。以下两个示例不可复制,但可以移动:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

对比:

struct A
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

在多重继承下,甚至会出现空间惩罚:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
};

struct D
    : public B,
      public C,
      private boost::noncopyable
{
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

对我来说这是打印出来的:

3

但是这个,我认为有更好的文档:

struct A
{
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
    C(const C&) = delete;
    C& operator=(const C&) = delete;
};

struct D
    : public B,
      public C
{
    D(const D&) = delete;
    D& operator=(const D&) = delete;
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

输出:

2

我发现声明我的复制操作比推理我是否多次从boost::non_copyable 派生以及这是否会让我付出代价要容易得多。特别是如果我不是完整继承层次结构的作者。

【讨论】:

  • 公平地说,boost::noncopyable 早在 C++11 之前就已经可用,并且编译支持 = delete。我同意你的观点,对于 C++11 近乎兼容的编译器,它现在已经过时了。
  • 某人有个好主意,将noncopyable 设为CRTP 基类,这样层次结构中的所有基类都是唯一的。
  • 另一个缺点是private: __copy_constructor__; 是完全可移植的,您不需要 ~40 MB 的 Boost 依赖项。
  • 这就提出了一个问题:在 boost 中还有什么被 C++11 淘汰了?
  • @Jon:这个问题没有硬性和快速的答案。但是(仅作为示例)我会考虑在使用boost::ptr_vector&lt;animal&gt;boost.org/doc/libs/1_54_0/libs/ptr_container/doc/tutorial.html)之前使用std::vector&lt;std::unique_ptr&lt;animal&gt;&gt;。基本原理:如果我知道vector,并且我知道unique_ptr,那么我就知道unique_ptr 向量的语义。而且我知道 std::algorithms (例如排序)如何与之交互。我不必完全了解新容器及其成员算法(例如成员排序)。
【解决方案2】:

总结别人说的话:

boost::noncopyable 相对于私有复制方法的优势

  1. 它的意图更加明确和描述性。使用私有复制函数是一个比noncopyable 需要更长的时间来发现的习惯用法。
  2. 更少的代码/更少的输入/更少的混乱/更少的错误空间(最简单的可能是意外提供一个实现)。
  3. 它在类型的元数据中嵌入含义,类似于 C# 属性。您现在可以编写一个只接受不可复制对象的函数。
  4. 它可能会在构建过程的早期捕获错误。错误将在编译时而不是链接时出现,以防类本身或类的朋友进行错误复制。
  5. (几乎与#4 相同)防止类本身或类的朋友调用私有复制方法。

私有复制方法相对于boost::noncopyable的优势

  1. 无提升依赖性

【讨论】:

  • 还有一个空间劣势,正如@Howard Hinnant 所指出的那样
【解决方案3】:

它使意图明确和明确,否则必须查看类的定义,并搜索与复制语义相关的声明,然后查找其中的访问说明符已声明,以确定该类是否不可复制。通过编写需要启用复制语义的代码并查看编译错误来发现它的其他方法。

【讨论】:

  • 您无需查看定义即可看到复制运算符在声明中是私有的。
  • @spraff:这就是类的定义。类的定义包含所有声明的成员。
  • 为了更深入地挖掘,显式的优点之一是含义现在嵌入在类型名元数据中。现在你可以编写一个函数,例如只接受不可复制的对象。
  • 如果您无权访问类定义,那么它就是一个不完整的类型,您不能真正将它用于任何事情。如果没有这个定义,你也看不到它继承了noncopyable。所以这是一个有争议的问题。
  • @spraff:我不明白你所说的技术差异是什么意思。我说过这样的话吗?
【解决方案4】:
  1. boost::noncopyable 的意图更加清晰。
  2. Boost::noncopyable 可防止类方法意外使用私有复制构造函数。
  3. 使用 boost::noncopyable 的代码更少。

【讨论】:

    【解决方案5】:

    我不明白为什么似乎没有其他人提到它,但是:

    使用noncopyable,您只需写一次班级名称。

    没有,五重复制: 一个 A 用于“class A”,两个用于禁用赋值,两个用于禁用复制构造函数。

    【讨论】:

    • 你是说它不可复制,这样增加了可读性并且可以搜索。
    【解决方案6】:

    引用文档:

    “处理这些的传统方法是声明一个私有的复制构造函数和复制赋值,然后记录为什么这样做。但是从 noncopyable 派生更简单、更清晰,并且不需要额外的文档。 "

    http://www.boost.org/libs/utility/utility.htm#Class_noncopyable

    【讨论】:

      【解决方案7】:

      一个具体的优势(除了更清楚地表达您的意图之外)是,如果成员或友元函数试图复制对象,则在编译阶段而不是链接阶段会更快地发现错误。基类构造函数/赋值在任何地方都无法访问,导致编译错误。

      它还可以防止您意外定义函数(即键入{} 而不是;),这是一个很可能被忽视的小错误,但随后会允许成员和朋友制作对象的无效副本。

      【讨论】:

      • 这就是我要找的 ;)
      • @Mike:...is that the error will be caught sooner, at the compile stage not the link stage。具体如何?即使boost::noncopyable 会做同样的事情,如果你不使用它。
      • @Nawaz:如果你不使用noncopyable 基类,那么你在你的类中声明一个私有构造函数。类的成员和朋友可以访问 ,因此没有编译错误——只是由于缺少定义而导致的链接错误。 (除非你不小心提供了一个定义——使用基类也可以防止这个错误)。
      • 因为 noncopyable 具有 private 复制功能,子类根本无法访问它们 - 因此编译器错误。如果将函数放在子类中,则可以访问它们,因此它们在链接器发现它们未定义之前是有效的。
      • @MikeSeymour:好的。它只与会员和朋友有关。我没有考虑他们。好点。但从实际的角度来看,这几乎没有什么优势,因为现代 IDE 或所谓的编译器都是按顺序执行的,这意味着你得到的都是错误。
      【解决方案8】:

      一个小的缺点(特定于 GCC)是,如果你使用 g++ -Weffc++ 编译你的程序并且你有包含指针的类,例如

      class C : boost::noncopyable
      {
      public:
        C() : p(nullptr) {}
      
      private:
        int *p;
      };
      

      GCC 不明白发生了什么:

      警告:“C 类”具有指针数据成员 [-Weffc++]
      警告:但不会覆盖 'C(const S&)' [-Weffc++]
      警告:或 'operator=(const C&)' [-Weffc++]

      虽然它不会抱怨:

      #define DISALLOW_COPY_AND_ASSIGN(Class) \
        Class(const Class &) = delete;     \
        Class &operator=(const Class &) = delete
      
      class C
      {
      public:
        C() : p(nullptr) {}
        DISALLOW_COPY_AND_ASSIGN(C);
      
      private:
        int *p;
      };
      

      PS 我知道 GCC 的 -Weffc++ 有几个问题。无论如何,检查“问题”的代码非常简单......有时它会有所帮助。

      【讨论】:

        【解决方案9】:

        优点是您不必自己编写私有复制构造函数和私有复制运算符,它清楚地表达了您的意图,而无需编写额外的文档。

        【讨论】:

          【解决方案10】:

          我宁愿使用 boost::noncopyable 而不是手动删除或私有化复制构造函数和赋值运算符。

          但是,我几乎从不使用 either 方法,因为:

          如果我正在制作一个不可复制的对象,那么它是不可复制的肯定是有原因的。这个原因,99% 的时间,是因为我的成员无法有意义地复制。很有可能,这些成员也更适合作为私有实现细节。所以我制作了大多数这样的类:

          struct Whatever {
            Whatever();
            ~Whatever();
            private:
            struct Detail;
            std::unique_ptr<Detail> detail;
          };
          

          所以现在,我有一个私有实现结构,并且由于我使用了 std::unique_ptr,我的顶级类是不可免费复制的。来自此的链接错误是可以理解的,因为它们谈论您如何无法复制 std::unique_ptr。对我来说,这就是 boost::noncopyable 和私有实现合二为一的所有好处。

          这种模式的好处是以后,如果我确定我确实想让我的这个类的对象可复制,我可以添加和实现一个复制构造函数和/或赋值运算符而不改变类层次结构。

          【讨论】:

          • unique_ptr 给出的印象细节可以为空。
          • 可能是 null unique_ptr 不能吗?至少 Boost 作用域指针有一个空的构造函数来处理 null - 不知道 std::unique_ptr。
          【解决方案11】:

          根据 Scott Meyers 的说法,缺点是“非自然”,如果您确实需要找到它的缺点。

          【讨论】:

            猜你喜欢
            • 2012-03-05
            • 2011-06-17
            • 2011-03-31
            • 1970-01-01
            • 1970-01-01
            • 2014-10-11
            • 2013-09-13
            • 1970-01-01
            相关资源
            最近更新 更多