boost ::不可复制的优点是什么


70

为了防止复制类,您可以非常容易地声明一个私有复制构造函数/赋值运算符。但是您也可以继承boost::noncopyable

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


18
请注意,在C ++ 11中,您将编写struct Foo{Foo(const Foo&)=delete;};
spraff 2011年

2
我认为这主要是因为普通的peon无法理解为什么您的副本构造函数是私有且未定义的。
奥斯卡·科尔兹

4
@spraff我相信您也需要Foo & operator=(const Foo &) = delete;吗?
wjl 2011年

是。这只是一个示例,而不是完整的实现。
spraff 2011年

Answers:


52

我看不出任何文档方面的好处:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

vs:

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;
};

vs:

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多次衍生以及是否要花我多得多。特别是如果我不是完整继承层次结构的作者。


25
公平地说,boost::noncopyable它早在C ++ 11之前就已经存在,并且对进行了编译支持= delete。我确实同意您的观点,即对于C ++ 11接近兼容的编译器,现在已经过时了。
Matthieu M. 2012年

6
有人有一个好主意,并制作了noncopyable一个CRTP基类,这样层次结构中的所有基类都是唯一的。
Johannes Schaub-litb 2012年

3
另一个缺点是它private: __copy_constructor__;是完全可移植的,并且不需要〜40 MB的Boost依赖项。
Luis Machuca

2
这就提出了一个问题:C ++ 11使boost中还有什么过时的?
2012年

2
@乔恩:这个问题没有一成不变的答案。但是(仅作为示例)我会考虑std::vector<std::unique_ptr<animal>>在到达之前使用boost::ptr_vector<animal>boost.org/doc/libs/1_54_0/libs/ptr_container/doc/tutorial.html)。原理:如果我知道vector,并且我知道unique_ptr,那么我知道unique_ptr向量的语义。而且我知道std :: algorithms(例如sort)如何与之交互。我不必学习有关其成员算法(例如,成员排序)的新容器的全部知识。
Howard Hinnant 2013年

43

它使意图明确和清晰的,否则,人们必须看到类的定义,并搜索相关的拷贝语义的声明,然后寻找其被访问说明符声明,以确定是否该类是否不可复制。通过编写要求启用了复制语义的代码来发现它的其他方法,并查看编译错误。


4
您无需查看定义即可看到复制操作符在声明中是私有的。
spraff 2011年

7
@spraff:这称为类的定义。类的定义包含所有已声明的成员。
纳瓦兹

1
更深入地讲,显式的优点之一是现在含义已嵌入到类型名元数据中。现在,您可以编写一个仅接受不可复制对象的函数。
2011年

3
如果您无权访问类定义,那么它是不完整的类型,您实际上不能将其用于任何东西。没有这个定义,您将看不到它也是继承的noncopyable。所以这是有争议的。
spraff 2011年

5
@spraff:我不明白您所说的技术差异。我有说什么吗?
Nawaz

42

总结别人说的话:

boost::noncopyable优于私有复制方法的优点

  1. 它在意图上更明确和更具描述性。使用私人复制功能是一个习惯,比发现需要更长的时间noncopyable
  2. 更少的代码/更少的键入/更少的混乱/更少的错误空间(最容易的是偶然地提供了实现)。
  3. 它将含义直接嵌入类型的元数据中,类似于C#属性。现在,您可以编写一个仅接受不可复制对象的函数。
  4. 它可能会在构建过程的早期发现错误。如果类本身或类的朋友正在执行错误的复制,则错误将在编译时而不是链接时显示。
  5. (与#4几乎相同)防止类本身或类的朋友调用私有复制方法。

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

  1. 没有助推依赖

10
正如@Howard Hinnant所指出的那样,这也存在空间上的不利之处
Philip

16
  1. boost :: noncopyable的意图更加清晰。
  2. Boost :: noncopyable防止类方法意外使用私有副本构造函数。
  3. boost :: noncopyable的代码更少。

16

我不明白为什么没人能提到它,但是:

noncopyable您一起写下您的班级名称一次。

没有,则进行五重复制:一个用于“类A”的A,两个用于禁用分配的属性,以及两个用于禁用复制构造函数的属性。


1
并且您说它是不可复制的,从而增加了可读性并且可以搜索。
hochl


8

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

它还可以防止您意外地定义函数(即,键入{}而不是;),这是一个很小的错误,很可能不会引起注意,但是会允许成员和朋友创建该对象的无效副本。


那就是我一直在寻找的东西;)
2011年

@Mike: ...is that the error will be caught sooner, at the compile stage not the link stage。到底如何 即使boost::noncopyable您不使用它,也会执行相同的操作。
纳瓦兹

@Nawaz:如果不使用noncopyable基类,则在类中声明一个私有构造函数。这从类的成员和朋友的访问,所以没有编译错误-只是一个链接错误,由于缺少定义。(除非您不小心提供了定义-使用基类也将防止该错误)。
Mike Seymour

1
由于不可复制具有私有复制功能,因此子类根本无法访问它们-从而导致编译器错误。如果将函数放在子类中,则可以访问它们,因此它们在链接器看到未定义它们之前一直有效。
2011年

@MikeSeymour:好吧。它仅涉及成员和朋友。我没想到他们。好点。但是从实际的角度来看,这几乎没有优势,因为现代的IDE或所谓的编译器都按顺序进行,这意味着您会出错。
纳瓦兹

3

好处是您不必自己编写私有副本构造函数和私有副本运算符,并且无需编写其他文档即可清楚地表达您的意图。


3

一个缺点(特定于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 ++有几个问题。无论如何,检查“问题”的代码非常简单……有时会有所帮助。


2

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

但是,我几乎从不使用任何一种方法,因为:

如果我要制作不可复制的对象,则必须有一个不可复制的原因。99%的原因是因为我的成员无法被有意义地复制。这样的成员很可能也更适合作为私有实施细节。因此,我制作了大多数这样的类:

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

因此,现在,我有了一个私有实现结构,并且由于使用了std :: unique_ptr,所以我的顶级类是免费不可复制的。由此产生的链接错误是可以理解的,因为它们讨论了如何无法复制std :: unique_ptr。对我而言,这就是boost :: noncopyable的全部好处,并且私有实现集成为一个。

这种模式的好处是以后,如果我确定确实要使该类的对象可复制,则可以在不更改类层次结构的情况下添加并实现一个复制构造函数和/或赋值运算符。


unique_ptr给人的印象细节可以为空。
维克多·瑟尔2014年

可能是空的unique_ptr不可以吗?至少Boost范围指针具有一个空的构造函数来处理null-关于std :: unique_ptr的dunno。
agodinhost

1

根据斯科特·迈耶斯(Scott Meyers)的说法,如果您确实需要找到它,则名称为“非自然”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.