C++中避免复制机制

8
我正在学习C++,之前有扎实的C基础。为了避免reddit和hacker news上C++的错误,我一直在使用Google C++样式指南和LLVM源代码作为自己代码的参考。其中一个引人注目的问题是这两个项目都使用以下代码。以下内容摘自LLVM的include/Support/MemoryBuffer.h:

可能重复:
为什么拷贝构造函数和赋值运算符被禁止?

MemoryBuffer(const MemoryBuffer &); // DO NOT IMPLEMENT
MemoryBuffer &operator=(const MemoryBuffer &); // DO NOT IMPLEMENT

谷歌回声了这种用法。显然,禁用这些“复制构造函数”是一件好事。

那么我的问题是:为什么这些东西如此可怕,如果它们没有受到保护,它们的使用会是什么样子,对代码有何影响?


14
Google的样式指南是有史以来最糟糕的样式指南之一。 - Cat Plus Plus
13
不涉及您的核心问题,在学习Google的代码风格指南时不要试图汲取任何东西。该指南旨在促进与现有Google代码良好接口的代码,而非推广优秀的C++编程。除非您是Google的员工,否则这不是您应该追求的目标。 - Dennis Zickefoose
1
尝试获取《Effective C++》第二版,这是一本针对从C转向C ++的程序员编写的非常优秀的书籍。(更新的第三版是针对从Java、C#等语言转向C++的程序员编写的,因此不太适合。) - sbi
1
@GWW:“(新的第三版是针对从Java、C#等语言转来的程序员,所以不太适合。)” - Dennis Zickefoose
3
“init”方法。不支持运算符重载(以及其他一些荒谬的重载规则)。不支持异常处理。使用getters/setters获取和设置属性。输出参数作为指针传递。不允许默认参数。不支持流操作。不支持C++11。 - Cat Plus Plus
显示剩余6条评论
4个回答

9
当一个对象必须管理自己的资源,如内存或系统句柄时,那么默认的复制构造函数和赋值运算符将不再适用。这意味着您必须重写它们。
然而,有时复制这样的对象是没有意义的。或者说,有些对象不应该被复制。有时甚至不可能编写这样的构造函数或赋值运算符。在这种情况下,最好禁用复制和赋值,以确保它们不会被意外复制。
标准的iostream就是一个很好的例子。
总之,这是一个特殊情况。绝对不是经常会遇到的事情。

1
一个很好的例子是持有特殊资源(如文件句柄)的对象。 - GWW
3
在C++03中,很难避免一些复制语义(例如如何从函数中返回对象),可以使用锁、某些类型的智能指针或某些类型的智能指针。在C++11中,移动语义可以让你完全避免复制对象。 - Alexandre C.

6

拷贝构造器并不可怕,不应该将其视为什么恐怖的事情。

然而,在某些情况下,复制一个对象根本就没有意义。例如你提供的例子中,内存缓存是一个很好的例子,它不需要被复制。也许它已经用于存储分配对象的数据。那么复制会提供什么呢?只有所有这些对象的原始数据的副本,除此之外几乎没有其他作用(例如对象无法使用它来释放内存)。

因此,一旦我们已经决定不复制我们的类,也就意味着我们应该防止程序员这样做。如果我们不声明它们,编译器会自动为我们生成默认的拷贝构造器和赋值运算符。所以,如果我们明确声明了它们(不需要提供实现),并确保这些声明是私有的,那么如果程序员试图进行复制操作,编译器会报错。


3
在C++03中,声明一个没有定义的拷贝构造函数和赋值运算符为私有是一种防止人们复制类实例的方法。如果有人试图这样做,他们将会得到一个编译错误,指出赋值运算符和拷贝构造函数是私有的。此外,如果类的自身方法尝试使用这些函数而未提供定义,则会得到一个链接器错误,指出运算符和拷贝构造函数未定义。这是必要的,因为否则编译器将为您生成一个拷贝构造函数和赋值运算符,通常不会做你想要的事情(浅拷贝)。
在新的C++11标准中,有一种更好的方法来实现这个。可以在声明中使用delete关键字,如下所示:
struct NoCopy
{
    NoCopy & operator =( const NoCopy & ) = delete;
    NoCopy ( const NoCopy & ) = delete;
};
NoCopy a;
NoCopy b(a); //compilation error, copy ctor is deleted

以下内容摘自这里


3
有时候,复制对象并没有意义(例如一个“std::unique_ptr”,一个互斥锁对象或一个数据库连接对象等)。或者复制操作效率低下,你希望避免这种情况。或者正确实现复制构造函数非常繁琐且易出错,你更愿意不依赖于其存在。通过将复制构造函数声明为私有,可以删除复制语义。
另一个流行的选择是从“boost::non_copyable”继承。
C++11标准提供了一个替代复制构造函数的方法。这被称为“移动语义”,允许你移动对象而不是复制它们。移动语义几乎总是有意义的:例如从函数返回一个对象就会移动它。你还可以将一个对象明确地移动到一些按值取参数的函数中。
请注意,C++11在原则上允许你以这种方式删除复制语义:
struct foo
{
    foo(const foo&) = delete;
    void operator=(const foo&) = delete;
};

与C++03不同,现在我不再将它们设置为私有。

现在每当我需要声明和实现一个复制构造函数时,我几乎总是禁用复制语义。这个习惯有些讲究:如果复制语义不是平凡的,那么复制可能存在问题。然而,引用计数是一个值得注意的例外(参见类模板std::shared_ptr)。


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