如何使这个 C++ 对象不可复制?
Posted
技术标签:
【中文标题】如何使这个 C++ 对象不可复制?【英文标题】:How do I make this C++ object non-copyable? 【发布时间】:2011-01-11 12:55:12 【问题描述】:见标题。
我有:
class Foo
private:
Foo();
public:
static Foo* create();
我需要从这里做什么才能使 Foo 不可复制?
谢谢!
【问题讨论】:
【参考方案1】: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
【讨论】:
作为一个有趣的问题,你为什么将默认构造函数设为私有,并添加一个 create() 方法?这种布局有什么优势? @EdwardBird 我只是在使用问题示例。这种做法基本上就像通过工厂强制构造特定类型的实例。如果构造函数应该在提供对象之前或什至在创建对象之前(可能是一些内存池操作)之前必须完成基本设置和一些其他操作(可能因上下文或平台或其他原因而有所不同),这将很有用。我个人会使用 unique_ptr 或 shared_ptr 作为 create() 返回类型。无论如何,主要原因只是修复问题示例。 禁用复制构造和复制赋值运算符也会禁用移动构造和赋值。移动操作仍将通过回退到复制来起作用。通过将它们显式设置为“默认”来重新启用它们。需要注意的事项。 @Ash - 重要的问题,但如果副本已被删除,移动将如何回退到副本? 最好将删除的方法放到公共部分。【参考方案2】:将复制构造函数和赋值运算符也设为私有。只需声明即可,您不必提供实现。
【讨论】:
【参考方案3】:#include <boost/utility.hpp>
class Foo : boost::noncopyable ...
但正如 Scott Meyers 曾经说过的那样......“这是一门很好的课程,只是我觉得这个名字有点不自然,错误不自然”,或者类似的东西。
【讨论】:
引用上下文的任何链接? 参考:Effective C++ (Third Edition) - Scott Meyers,第 6 条【参考方案4】:在 C++11 中,您可以通过在声明后放置 = delete
来显式禁用默认复制和赋值构造函数的创建。
来自Wikipedia:
struct NonCopyable
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable & operator=(const NonCopyable&) = delete;
;
当然,课程也是如此。
【讨论】:
【参考方案5】:另一种禁止复制构造函数的方法,为方便起见,可以使用 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);
;
ref from google style sheet
【讨论】:
您的解决方案不适用于某些编译器。一些 C++ 编译器要求,如果您声明一个类成员函数,那么您还必须定义它,即使它从未在代码中使用过。因此,您需要将 与上面的两个函数声明一起使用。 @ThreeBit,如果你的意思是构造函数有一个参数和析构函数,说“两个函数”,这些都是声明,程序员已经知道这些将在其他地方有定义。除此之外,它与接受的答案相同。 @ThreeBit:你指的是哪些编译器?如果他们这样做,则不符合标准。 @Sebastian 这不是真的。如果声明了类构造函数,则必须在某个地方定义它。如果函数没有被显式调用,一些流行的编译器允许你省略类成员函数的定义,这是该语言的编译器扩展。 Green Hills C++ 编译器是在这方面严格的一个示例。我在 C++ 标准中找不到任何位置要求编译器在不使用成员函数时跳过它的链接。随意找到这样的条款。也许你能找到一个。 @ThreeBit:有趣的讨论。我在标准中发现:“程序中的非内联成员函数应该最多一个定义;不需要诊断。”。然后,对于本地类:“本地类的成员函数应在其类定义中内联定义,如果它们被定义的话。”。我没有找到任何禁止没有相应定义的成员函数声明的内容。【参考方案6】:在这里添加一点。
如前所述,传统的解决方案是声明Copy Constructor
和Assignment Operator
为private
,不定义 他们。
private
,这将导致任何试图使用它们但无权访问类的私有部分的人编译时错误...
留下的朋友(和班级本身)会在undefined symbol
的形式下发生错误,无论是在链接时间(如果您检查那里的那些)或最有可能在运行时(尝试加载库时)。
当然,在第二种情况下会很麻烦,因为您没有指示发生错误的文件和行,因此您必须自己检查代码。幸运的是,它仅限于您的类方法和朋友。
此外,值得注意的是,这些属性在继承和组合的道路上是可传递的:编译器只会生成默认版本的Default Constructor
、Copy Constructor
、Assignment Operator
和 Destructor
,如果它可能。
这意味着对于这四个中的任何一个,它们只有在类的所有基和属性都可以访问时才会自动生成。
// What does boost::noncopyable looks like >
class Uncopyable
public:
Uncopyable()
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
;
这就是为什么从此类继承(或将其用作属性)将有效地防止您自己的类可复制或可分配,除非您自己定义这些运算符。
通常选择继承而不是组合有两个原因:
对象实际上是Uncopyable
,即使多态性可能没有那么有用
继承导致EBO
或Empty Base Optimization
,而属性将是可寻址的,因此即使它实际上并不需要它也会占用内存(在类的每个实例中),编译器有可能不添加基类的开销。
您也可以将运算符声明为私有而不在您自己的类中定义它们,但代码会较少自文档化,并且您将无法自动搜索这些类那么就拥有这个属性(除非你有一个成熟的解析器)。
希望这能阐明机制。
【讨论】:
顺便说一句,如果没有明确定义构造函数,Uncopyable
不是不完整的,因为它不会因为其他构造函数的存在而自动生成?例如,您会得到“没有合适的默认构造函数可用”:rextester.com/SFWR22041 感谢您的有用回答!我特别感谢您为使用继承提供的动力。
@chappjc:你又说对了,我真的应该编译代码。
@MatthieuM。我不同意“这就是为什么从此类继承(或将其用作属性)将有效地防止您自己的类可复制或可分配的原因,除非您自己定义这些运算符。”因为即使明确定义复制构造函数和赋值运算符,由于继承层次结构,仍然会强制执行不可复制的特性。
@johnsmith:恐怕你对规则感到困惑。我可以向你保证它工作得很好,只是建议你尝试一下(例如在 ideone 或 Coliru 上)。
@MatthieuM。很抱歉让你的泡沫破灭,check 自己试试。之后,请编辑您的答案以说明正确的事实。编译器不允许您定义自己的复制构造函数并在您从不可复制的类继承时使用它【参考方案7】:
使 C++ 对象不可复制的典型方法是显式声明复制构造函数和复制赋值运算符,但不实现它们。这将阻止编译器生成自己的。 (通常这与声明它们一起完成 private
以便它生成编译错误而不是链接器错误。)
还有可以继承的 boost::noncopyable
类,它完成了我上面描述的操作。
【讨论】:
【参考方案8】:C++11 中的良好做法是将复制构造函数和赋值声明为公开删除。 未私下删除,公开已删除:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-delete
【讨论】:
【参考方案9】:将复制构造函数设为私有。
Foo(const Foo& src);
不需要实现,在头文件中声明即可。
【讨论】:
【参考方案10】:这是我用的:
/* 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
;
【讨论】:
请注意,从这样的实用程序类继承可能会对类大小产生不利影响,具体取决于架构 ABI。有关详细信息,请参阅trac.webkit.org/changeset/68414。诚然,那个变更集只提到了 Itanic,没有别的——但值得依赖没有其他架构这样做过吗?可能是。这是一个明确的风险,并且声明私有构造函数和赋值运算符同样有效。以上是关于如何使这个 C++ 对象不可复制?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 CSS 或 jquery 使链接不可点击? [复制]