复制对象意味着什么?什么是复制构造函数和复制赋值运算符?我什么时候需要自己申报?如何防止复制对象?


当前回答

许多现有的答案已经涉及到复制构造函数、赋值运算符和析构函数。然而,在后C++11中,移动语义的引入可能会将其扩展到3之外。

最近,Michael Claisse做了一个涉及这个话题的演讲:http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class

其他回答

三巨头的法则如上所述。

一个简单的例子,用简单的英语来说,就是它解决的问题:

非默认析构函数

您在构造函数中分配了内存,因此需要编写一个析构函数来删除它。否则会导致内存泄漏。

你可能认为这已经完成了。

问题是,如果复制对象,则复制对象将指向与原始对象相同的内存。

一旦其中一个删除了其析构函数中的内存,另一个将有一个指向无效内存的指针(这称为悬空指针),当它试图使用它时,事情就会变得复杂起来。

因此,您编写了一个复制构造函数,以便它为新对象分配自己的内存以进行销毁。

赋值运算符和复制构造函数

您在构造函数中为类的成员指针分配了内存。复制此类的对象时,默认赋值运算符和复制构造函数会将此成员指针的值复制到新对象。

这意味着新对象和旧对象将指向同一块内存,因此当您在一个对象中更改它时,另一个对象也会更改它。如果一个对象删除了这个内存,另一个对象将继续尝试使用它。

要解决这个问题,您需要编写自己版本的复制构造函数和赋值运算符。您的版本为新对象分配单独的内存,并跨第一个指针所指向的值而不是其地址进行复制。

许多现有的答案已经涉及到复制构造函数、赋值运算符和析构函数。然而,在后C++11中,移动语义的引入可能会将其扩展到3之外。

最近,Michael Claisse做了一个涉及这个话题的演讲:http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class

基本上,如果你有一个析构函数(不是默认的析构函数),这意味着你定义的类有一些内存分配。假设某个客户机代码或您在外部使用该类。

    MyClass x(a, b);
    MyClass y(c, d);
    x = y; // This is a shallow copy if assignment operator is not provided

如果MyClass只有一些基本类型的成员,则默认的赋值运算符可以工作,但如果它有一些指针成员和没有赋值运算符的对象,则结果将是不可预测的。因此,我们可以说,如果类的析构函数中有要删除的内容,我们可能需要一个深度复制运算符,这意味着我们应该提供一个复制构造函数和赋值运算符。

三法则是C++的经验法则,基本上说

如果您的班级需要复制构造函数,分配运算符,或析构函数,如果定义明确,那么很可能需要所有三个。

原因是这三个类通常都用于管理资源,如果您的类管理资源,则通常需要管理复制和释放。

如果没有良好的语义来复制类管理的资源,那么考虑通过将复制构造函数和赋值运算符声明为私有来禁止复制。

(注意,即将发布的新版本的C++标准(即C++11)为C++添加了移动语义,这可能会改变三规则。然而,我对这一点了解太少,无法编写一篇关于三法则的C++11章节。)

我什么时候需要自己申报?

三人法则规定,如果您声明

复制构造函数复制赋值运算符析构函数

那么你应该申报这三项。这是由于观察到,需要接管复制操作的含义几乎总是源于执行某种资源管理的类,这几乎总是意味着

无论在一个复制操作中进行了什么资源管理,都可能需要在另一复制操作中完成,并且类析构函数也将参与资源的管理(通常是释放资源)。要管理的经典资源是内存,这就是为什么所有标准库类管理内存(例如,执行动态内存管理的STL容器)都声明“三大”:复制操作和析构函数。

“三法则”的一个结果是,用户声明的析构函数的存在表明简单的按成员复制不太适合类中的复制操作。这反过来表明,如果类声明了析构函数,那么复制操作可能不会自动生成,因为它们不会做正确的事情。在采用C++98时,这一推理方式的重要性尚未得到充分认识,因此在C++98中,用户声明的析构函数的存在对编译器生成复制操作的意愿没有影响。C++11中的情况仍然如此,但这仅仅是因为限制复制操作的生成条件会破坏太多的遗留代码。

如何防止复制对象?

将复制构造函数和复制赋值运算符声明为私有访问说明符。

class MemoryBlock
{
public:

//code here

private:
MemoryBlock(const MemoryBlock& other)
{
   cout<<"copy constructor"<<endl;
}

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
 return *this;
}
};

int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}

在C++11以后的版本中,您还可以声明复制构造函数和赋值运算符已删除

class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete

// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};


int main()
{
   MemoryBlock a;
   MemoryBlock b(a);
}