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


69

参见标题。

我有:

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

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

谢谢!

Answers:


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

如果您使用的是boost,还可以从不可复制继承: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()方法?这种布局有什么优势?
user3728501 2013年

@EdwardBird我只是使用问题示例。这种做法基本上就像通过工厂强制构造特定类型的实例一样。如果构造函数应该进行基本设置,并且在提供对象之前甚至在创建对象之前(可能需要一些内存池操作)必须进行一些其他操作(可能因上下文,平台或其他因素而有所不同),这将非常有用。我本来会使用unique_ptr或shared_ptr作为create()返回类型。无论如何,主要的原因只是修复问题示例。
克莱姆

6
禁用副本构造和副本分配运算符也将禁用移动构造和分配。通过退回到复制,移动操作仍将起作用。通过将它们明确设置为“默认”来重新启用它们。有一点要注意。
2015年

1
@Ash-重要的收获,但是如果副本已被删除,将如何回退到副本?
尼克

4
将删除的方法放入公共部分是一个好习惯。
约旦

26

也将复制构造函数和赋值运算符设为私有。仅声明就足够了,您不必提供实现。


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

但是正如斯科特·迈耶斯(Scott Meyers)曾经说过的那样……“这是一个很好的课程,只是我发现这个名称有点不合常理,而且很不自然”,或者类似的意思。


链接到报价的上下文吗?
GManNickG'2

4
参考:有效C ++(第三版) -斯科特迈尔斯,项目6
Thirler

18

禁止复制构造函数的另一种方法,为方便起见,可以使用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);
};

谷歌样式表的参考


1
您的解决方案不适用于某些编译器。一些C ++编译器要求,如果声明一个类成员函数,则也必须对其进行定义,即使它从未在代码中使用过。因此,您需要在上面的两个函数声明中使用{}。
ThreeBit

@ThreeBit,如果您通过说“两个函数”来表示具有一个参数的构造函数和一个析构函数,则它们是派生对象,程序员已经知道它们将在其他地方具有定义。除此之外,它与接受的答案相同。
弗雷德里克·高斯

1
@ThreeBit:您是指哪些编译器?如果这样做,将不符合该标准。
塞巴斯蒂安·马赫

@塞巴斯蒂安那不是真的。如果声明了类构造函数,则必须在某处定义它。如果未显式调用函数,某些流行的编译器允许您忽略类成员函数的定义,这是该语言的编译器扩展。Green Hills C ++编译器就是这方面严格的示例。我找不到C ++标准中没有要求编译器跳过成员函数(如果未使用)的链接的位置。随时找到这样的条款。也许您可以找到一个。
ThreeBit

2
@ThreeBit:有趣的讨论。我在标准中发现:“程序中最多应该有一个非内联成员函数的定义;不需要诊断。” 。然后,对于本地类:“本地类的成员函数应该内联的类定义的,如果他们在所有的定义。。我找不到禁止没有相应定义的成员函数声明的任何内容。
塞巴斯蒂安·马赫

17

要添加一点。

传统的解决方案是,正如人们所说的,要宣布双方Copy ConstructorAssignment Operator作为private,而不是定义它们。

  • 因为它们是private,这将导致任何尝试使用它们但无法访问该类私有部分的人都出现编译时错误...
  • 这会使朋友(和类本身)undefined symbol链接时(如果在其中检查它们)或在运行时(尝试加载库时)以的形式出现错误,而导致错误(以及类本身)。

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


此外,值得注意的是,这些属性是传递下来继承和组合的道路:编译器将仅生成的默认版本Default Constructor,在Copy Constructor中,Assignment OperatorDestructor它是否可能。

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

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

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

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

通常,选择继承而不是那里的组成有两个原因:

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

或者,您可以声明运算符为私有,而不是在您自己的类中定义它们,但是代码的自记录性会降低,并且您将无法自动搜索那些具有该属性的类(除非您拥有完整的解析器)。

希望这能对机制有所启发。


顺便说一句,Uncopyable如果没有显式定义构造函数,是不是不完整,因为不会由于其他构造函数的存在而自动生成它?例如,您将获得“没有合适的默认构造函数”,它是:rextester.com/SFWR22041感谢您的帮助!我特别感谢您使用继承的动力。
chappjc 2015年

@chappjc:您又是对的,我真的应该编译代码。
Matthieu M.

@MatthieuM。我不同意以下说法:“这就是为什么从此类继承(或将其用作属性)将有效地防止您自己的类可复制或可分配,除非您自己定义这些运算符。” 因为由于继承层次结构,即使明确定义了复制构造函数和赋值运算符,仍将强制实施不可复制功能。
约翰·史密斯

@johnsmith:恐怕您对规则感到困惑。我可以向您保证它会完美运行,只是建议您尝试一下(例如在ideone或Coliru上)。
Matthieu M.

@MatthieuM。抱歉让您破灭,尝试一下。之后,请编辑您的答案以陈述正确的事实。编译器不会允许你定义自己的拷贝构造函数,并使用它,而你是从不可复制的类继承
约翰·史密斯

13

在C ++ 11中,可以通过放置= delete在声明之后来显式禁用默认副本和赋值构造函数的创建。

维基百科

struct NonCopyable {
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable & operator=(const NonCopyable&) = delete;
};

当然,上课也是如此。


4

使C ++对象不可复制的典型方法是显式声明一个复制构造函数和一个复制分配运算符,但不实现它们。这将阻止编译器生成自己的编译器。(通常,这是与声明它们一起进行的,private以便它生成编译错误而不是链接程序错误。)

还有boost::noncopyable一个您可以从其继承的类,它可以完成我上面描述的工作。



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。有关详细信息,请参见trac.webkit.org/changeset/68414。当然,该变更集仅提及Itanic,仅此而已-但值得一提的是,没有其他架构可以依赖吗?也许。这是绝对的风险,并且声明私有构造函数和赋值运算符同样有效。
杰夫·沃尔登

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.