如何使这个 C++ 对象不可复制?

80

请看标题。

我有:

class Foo {
   private:
     Foo();
   public:
     static Foo* create();
}

我需要从这里做什么才能使Foo无法复制?

10个回答

109
class Foo {
   private:
     Foo();
     Foo( const Foo& ); // non construction-copyable
     Foo& operator=( const Foo& ); // non copyable
   public:
     static Foo* create();
}

如果你正在使用boost,你还可以继承noncopyable:

http://www.boost.org/doc/libs/1_41_0/boost/noncopyable.hpp

编辑:如果您的编译器支持此功能,则可以使用C++11版本:

class Foo {
   private:
     Foo();
   public:
     Foo( const Foo& ) = delete; // non construction-copyable
     Foo& operator=( const Foo& ) = delete; // non copyable

     static Foo* create();
}
请注意已删除的方法应该是公开的:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-delete

3
有趣的是,你为什么将默认构造函数设为私有,并添加了一个 create() 方法?这种布局有哪些优点? - FreelanceConsultant
@EdwardBird 我只是举了一个问题的例子。这种做法基本上就像是通过工厂强制构建特定类型的实例。如果构造函数应该进行基本设置并且必须在提供对象之前执行一些其他操作(可能根据上下文、平台或其他因素而有所不同),甚至在创建对象之前执行一些操作(例如某些内存池操作),那么这种方法非常有用。个人而言,我会将unique_ptr或shared_ptr作为create()返回类型。无论如何,主要原因只是修复问题示例。 - Klaim
7
禁用拷贝构造函数和拷贝赋值运算符也会禁用移动构造函数和移动赋值运算符。通过回退到复制操作,移动操作仍然可以使用。通过显式地将它们设置为“默认”来重新启用它们。需要注意的是这一点。 - Ash
1
@Ash - 很重要的一点,但是如果复制已经被删除了,移动操作会如何回退到复制呢? - Nick
如果没有默认构造函数,我可以保持我的构造函数为公共的吗? - Danke Xie
4
把已删除的方法放在公共部分是一个好的做法。 - Jordan

30

将复制构造函数和赋值运算符也设为私有。只需声明即可,无需提供实现。


22
在C++11中,您可以通过在声明后面添加= delete来显式禁用默认复制和赋值构造函数。
来自维基百科:链接
struct NonCopyable {
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable & operator=(const NonCopyable&) = delete;
};
同样的道理也适用于类。

19
#include <boost/utility.hpp>
class Foo : boost::noncopyable {...

但正如Scott Meyers曾经说过的那样..."这是一个很好的类,只是我觉得名称有点不太自然",或者类似于这样的话。


引用的上下文链接在哪里? - GManNickG
5
参考书目:Effective C++(第三版)- Scott Meyers,条款6 - Thirler

19

另一种禁止复制构造函数的方法是使用DISALLOW_COPY_AND_ASSIGN宏:

// A macro to disallow the copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&) = delete;      \
  void operator=(const TypeName&) = delete

然后,在类Foo中:

class Foo {
 public:
  Foo(int f);
  ~Foo();

 private:
  DISALLOW_COPY_AND_ASSIGN(Foo);
};

来自Google编码规范的参考链接


1
你的解决方案在某些编译器上不能正常工作。一些C++编译器要求,如果你声明了一个类成员函数,那么即使它在代码中从未被使用过,你也必须定义它。因此,你需要在上面的两个函数声明中使用 {}。 - ThreeBit
@ThreeBit,如果你说“两个函数”指的是只有一个参数的构造函数和析构函数,那么这些是声明,程序员已经知道它们在其他地方有定义。除此之外,它与被接受的答案相同。 - Fredrick Gauss
1
@ThreeBit:你指的是哪些编译器?如果是这样,它们就不符合标准。 - Sebastian Mach
@Sebastian,那不是真的。如果声明了一个类构造函数,则必须在某个地方定义它。一些流行的编译器允许您省略类成员函数的定义,如果该函数没有被显式调用,则这是语言的编译器扩展。Green Hills C++编译器就是一个严格遵守这一点的例子。我找不到C++标准中要求编译器跳过成员函数链接的位置,如果它没有被使用。请随意查找此类条款。也许你可以找到一个。 - ThreeBit
2
@ThreeBit:有趣的讨论。我在标准中发现:“一个程序中非内联成员函数的定义最多只能有一个;不需要进行诊断。”。然后,对于局部类:“如果定义了局部类的成员函数,则它们必须在其类定义中定义为内联函数。”。我没有找到任何禁止没有相应定义的成员函数声明的内容。 - Sebastian Mach
显示剩余7条评论

17

在这里补充一下。

传统的解决方案就是,正如先前提到的那样,将复制构造函数和赋值运算符都声明为private,并不定义它们。

  • 因为它们是private的,所以任何试图使用它们但没有访问类私有部分权限的人都会在编译时发生错误......
  • 这只留给友元(和类本身),对于这些情况,错误将以undefined symbol的形式出现,无论是在链接时(如果你在链接时进行检查)还是最可能在运行时(尝试加载库时)。

当然,在第二种情况下,这很麻烦,因为你必须自己检查代码,因为你没有指示错误发生的文件和行。幸运的是,它仅限于你的类方法和友元。


此外,值得注意的是,这些属性沿着继承和组合道路是可传递的:只有当所有基类和类属性都可访问时,编译器才会生成Default ConstructorCopy ConstructorAssignment OperatorDestructor的默认版本。

这意味着对于这四个函数中的任何一个,只有当它们对类的所有基类和属性都是可访问的时,它们才会自动生成。

// What does boost::noncopyable looks like >
class Uncopyable {
public:
  Uncopyable() {}

private:
  Uncopyable(const Uncopyable&);
  Uncopyable& operator=(const Uncopyable&);
};

因此,从这个类继承(或将其用作属性)将有效地防止您自己的类可复制或可分配,除非您自己定义这些运算符。

通常选择继承而不是组合有两个原因:

  • 即使多态性可能没有那么有用,该对象也实际上是Uncopyable
  • 继承导致EBO空基类优化,而属性将是可寻址的,因此即使它实际上不需要,它也会占据内存(在类的每个实例中),编译器有可能不为基类添加此开销。

或者你可以将这些运算符声明为私有,并且不在自己的类中定义它们,但代码会更少“自我记录”,并且您将无法自动搜索具有此属性的类(除非您有一个完整的解析器)。

希望这能解释这个机制。


顺便提一下,如果没有显式定义构造函数,Uncopyable 不是不完整的吗?因为由于存在其他构造函数,它将不会自动生成。例如,您将使用此代码获得“没有适当的默认构造函数可用”:http://rextester.com/SFWR22041 感谢您有益的答案!我特别欣赏您提供的继承动机。 - chappjc
@chappjc:你说得对,我真的应该编译代码。 - Matthieu M.
@johnsmith:恐怕你对规则有些困惑。我可以向你保证它完美地运行,并建议你尝试一下(例如在ideone或Coliru上)。 - Matthieu M.
@MatthieuM。很抱歉打破你的幻想,请点击此处自行尝试。之后请编辑您的答案以说明正确的事实。当您从一个不可复制的类继承时,编译器将不允许您定义并使用自己的复制构造函数。 - john smith
@MatthieuM。结果证明你是正确的,我学到了东西 :) - john smith
显示剩余4条评论

7
典型的使C++对象无法被复制的方法是显式声明复制构造函数和复制赋值运算符但不实现它们。这将防止编译器生成自己的版本。(通常与将它们声明为私有一起使用,以便生成编译错误而非链接错误。)
还有一个boost::noncopyable类,你可以从中继承,它会执行上述操作。

5

3

将复制构造函数设为私有。

Foo(const Foo& src);

您不需要实现它,只需要在头文件中声明即可。


2

这是我使用的内容:

/* Utility classes */

struct NoCopy
{
public:
    NoCopy() {}
private:
    NoCopy(const NoCopy &);
};

struct NoAssign
{
private:
    NoAssign &operator=(const NoAssign &);
};

struct NonInstantiable
{
private:
    NonInstantiable();
};

struct NoCopyAssign : NoCopy, NoAssign
{
};
typedef NoCopyAssign NoAssignCopy;

在您的情况下:
struct Example : NoCopy
{
};

3
请注意,继承此类实用程序可能会对类大小产生不利影响,这取决于架构ABI。有关详细信息,请参见http://trac.webkit.org/changeset/68414。尽管该变更集仅提到Itanic,没有提到其他架构,但是否值得依赖其他架构从未执行此操作?也许是。这是一个明显的风险,声明私有构造函数和赋值运算符同样有效。 - Jeff Walden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接