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

How do I make this C++ object non-copyable?

提问人:anon 提问时间:2/1/2010 最后编辑:Vertexwahnanon 更新时间:5/3/2023 访问量:76865

问:

请参阅标题。

我有:

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

我需要做些什么才能使 Foo 不可复制?

C++ 构造函数不可复制

评论


答:

3赞 Roland Rabien 2/1/2010 #1

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

Foo(const Foo& src);

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

30赞 Hans Passant 2/1/2010 #2

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

7赞 jamesdlin 2/1/2010 #3

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

还有一个 boost::noncopyable 类,你可以从中继承,它执行我上面描述的。

109赞 Klaim 2/1/2010 #4
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赞 FreelanceConsultant 7/4/2013
有趣的是,为什么要将默认构造函数设为私有,并添加 create() 方法?这种布局有什么优点?
0赞 Klaim 7/4/2013
@EdwardBird我只是在用问题的例子。这种方式基本上就像通过工厂强制构建特定类型的实例一样。如果构造函数应该执行基本设置,并且必须在提供对象之前,甚至在创建对象之前(可能是一些内存池操作)之前必须完成一些其他操作(可能因上下文或平台或其他原因而异),这将非常有用。我个人会使用 unique_ptr 或 shared_ptr 作为 create() 返回类型。无论如何,主要原因只是修复问题示例。
7赞 Ash 7/8/2015
禁用复制构造和复制分配运算符也会禁用移动构造和分配。移动操作仍将通过回退到复制来发挥作用。通过将它们显式设置为“默认”来重新启用它们。需要注意的事情。
1赞 Nick 9/26/2015
@Ash - 重要的问题,但是如果已经删除了复制,将如何回退到复制?
4赞 Jordan 9/25/2019
最好将删除的方法放入公共部分。
19赞 Chris H 2/1/2010 #5
#include <boost/utility.hpp>
class Foo : boost::noncopyable {...

但正如斯科特·迈耶斯(Scott Meyers)曾经说过的那样......“这是一门很好的课,只是我觉得这个名字有点不自然,不自然”,或者类似的东西。

评论

0赞 GManNickG 2/1/2010
引用上下文的任何链接?
5赞 Thirler 3/7/2013
参考:有效的 C++(第三版)- Scott Meyers,第 6 项
17赞 Matthieu M. 2/1/2010 #6

在那里添加一点。

如前所述,传统的解决方案是同时声明 和 作为,而不是定义它们。Copy ConstructorAssignment Operatorprivate

  • 因为它们是 ,这将导致任何试图使用它们的人无法访问类的私有部分的编译时错误......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
  • 继承导致 或 ,虽然属性是可寻址的,因此即使它实际上并不需要它,也会占用内存(在类的每个实例中),但编译器有可能不为基类添加此开销。EBOEmpty Base Optimization

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

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

评论

0赞 chappjc 5/21/2015
顺便说一句,如果不显式定义构造函数,它不是不完整的,因为它不会因为其他构造函数的存在而自动生成?例如,你会得到“没有合适的默认构造函数可用”,这样可以: rextester.com/SFWR22041 感谢您的有用回答!我特别感谢你给予使用继承的动机。Uncopyable
0赞 Matthieu M. 5/21/2015
@chappjc:你又是对的,我真的应该编译代码。
0赞 john smith 8/11/2016
@MatthieuM。我不同意这样的说法:“这就是为什么从这个类继承(或将其用作属性)将有效地阻止你自己的类可复制或可赋值,除非你自己定义这些运算符。因为即使显式定义复制构造函数和赋值运算符,由于继承层次结构,仍将强制执行不可复制的功能。
0赞 Matthieu M. 8/11/2016
@johnsmith:恐怕你对规则感到困惑。我可以向你保证它运行良好,只是建议你尝试一下(例如在 ideone 或 Coliru 上)。
0赞 john smith 8/11/2016
@MatthieuM。对不起,戳破了你的泡沫,检查一下自己试试。之后,请编辑您的答案以陈述正确的事实。编译器将不允许你定义自己的复制构造函数,并在你从不可复制的类继承时使用它
2赞 Andreas Bonini 2/1/2010 #7

这是我使用的:

/* 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赞 Jeff Walden 12/17/2011
请注意,从这样的实用程序类继承可能会对类大小产生不利影响,具体取决于体系结构 ABI。有关详细信息,请参阅 trac.webkit.org/changeset/68414。诚然,这个变更集只提到了 Itanic,没有别的——但值得依赖其他架构来做这件事吗?或。这是一个明确的风险,声明私有构造函数和赋值运算符同样有效。
19赞 yesraaj 2/1/2010 #8

只是另一种禁止复制构造函数的方法,为了方便起见,可以使用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赞 ThreeBit 2/1/2013
您的解决方案无法按原样与某些编译器一起使用。某些 C++ 编译器要求,如果声明类成员函数,则还必须定义它,即使它从未在代码中使用过。因此,您需要将 {} 与上面的两个函数声明一起使用。
0赞 Fredrick Gauss 5/2/2015
@ThreeBit,如果你说“两个函数”来表示具有一个参数和析构函数的构造函数,那么这些是减值,程序员已经知道这些将在其他地方进行定义。除此之外,它与公认的答案相同。
1赞 Sebastian Mach 2/5/2017
@ThreeBit:你指的是哪些编译器?如果他们这样做,他们就不符合标准。
0赞 ThreeBit 2/6/2017
@Sebastian事实并非如此。如果声明了一个类构造函数,则必须在某个地方定义它。一些流行的编译器允许在未显式调用类成员函数的情况下省略该函数的定义,这是该语言的编译器扩展。Green Hills C++ 编译器就是在这方面严格执行的一个例子。我在 C++ 标准中找不到要求编译器跳过成员函数链接(如果不使用)的位置。随意找到这样的条款。也许你可以找到一个。
2赞 Sebastian Mach 2/7/2017
@ThreeBit:有趣的讨论。我在标准中发现:“程序中的非内联成员函数最多只能有一个定义;不需要诊断。然后,对于本地类:“如果本地类被定义,则应在其类定义中内联定义局部类的成员函数。我没有发现任何禁止没有相应定义的成员函数声明的内容。
22赞 bobbaluba 11/29/2012 #9

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

来自维基百科

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

当然,课程也是如此。

5赞 Matthieu Brucher 8/21/2018 #10

C++ 11 中的良好做法是将复制构造函数和赋值声明为公开删除。 未私下删除、公开删除:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-delete