C++ 防止对象复制的类
C++类的实例有时不应被克隆。防止对象复制有三种方法:非可复制混合类、私有复制构造函数和赋值运算符,或者删除这些特定成员函数。
将代表文件封装流的类的实例移动是不合适的。这将使实际的I/O系统处理变得复杂。类的实例拷贝指针是无用的,如果实例有特殊的私有对象。对象切割的问题是一种相对可比较的问题,但不完全是同一个主题。
下图中简单的Vehicle类旨在拥有一个唯一的所有者,即Person类的实例。
class Car {
public:
Car(): owner() {}
void setOwner(Person *o) { owner = o; }
Person *getOwner() const { return owner; }
void info() const;
private:
Person *owner;
};
实施人员之所以只需要做以下工作:
struct Person {
std::string name;
};
一个名为info()的辅助方法被实现用于说明问题,示例如下:
void Car::info() const
{
if (owner) {
std::cout < < "Owner is " << owner->name < < std::endl;
} else {
std::cout << "This car has no owner." << std::endl;
}
这个示例清楚地表明Car实例不能被克隆。例如,同一个车主不应该自动拥有一个相同车辆的副本。实际上,当执行下面的代码时:
Person joe;
joe.name = "Joe Sixpack";
Car sedan;
sedan.setOwner(&joe);
sedan.info();
Car anotherSedan = sedan;
anotherSedan.info();
将会输出:
Owner is Joe Sixpack
Owner is Joe Sixpack
如何停止这个无意拷贝的对象?
方法1:私有拷贝构造函数和拷贝赋值运算符
声明私有拷贝赋值运算符和拷贝构造函数是一种常用的策略。即使不需要实现它们。目的是确保任何尝试完成拷贝或赋值的操作都会导致编译错误。
汽车将被修改为在上面的示例中的样子。检查另外两个私有成员类。
class Car {
public:
Car(): owner() {}
void setOwner(Person *o) { owner = o; }
Person *getOwner() const { return owner; }
void info() const;
private:
Car(const Car&);
Car& operator=(const Car&);
Person *owner;
};
现在,如果我们尝试再次将Car的实例分配给一个新实例,编译器会大声抗议:
example.cpp:35:22: error: calling a private constructor of class 'Car'
Car anotherSedan = sedan;
^
example.cpp:22:3: note: declared private here
Car(const Car&);
^
1 error generated.
如果写下相同的名称会耗费太多时间,可以使用宏代替。WebKit在wtf/Noncopyable.h中使用了 WTF MAKE NONCOPYABLE 宏来实现这一点(请不要惊慌,在 WebKit 源代码的上下文中,这里的 WTF 代表 Web 模板框架)。Chrome 浏览器的代码中使用了 base/macros.h 文件中的 DISALLOW COPY 和 DISALLOW ASSIGN 宏,用于区分拷贝构造函数和赋值操作。
方法2:不可拷贝的 mixin
上述概念可以扩展为构建一个特定类,其主要功能是禁止对象的复制。它经常被称为 Noncopyable,并且经常用作 mixin。我们的示例中的 Car 类可以从这个 Noncopyable 类继承。
使用 Boost 的用户可能已经熟悉了 Boost 版本的上述 mixin,即 boost::noncopyable。以下是一个概念上独立的 mixin 实现的示例:
class NonCopyable
{
protected:
NonCopyable() {}
~NonCopyable() {}
private:
NonCopyable(const NonCopyable &);
NonCopyable& operator=(const NonCopyable &);
};
我们美妙的Car类的拼写如下:
Class Car: private NonCopyable {
public:
Car(): owner() {}
void setOwner(Person *o) { owner = o; }
Person *getOwner() const { return owner; }
}
private:
Person *owner;
};
使用Noncopyable的好处是相对于第一种选择,目标非常明显。只要看一眼类的初始行,就可以知道它的实例不应该被克隆。
方法3:删除复制赋值操作符和复制构造函数
对于较新的应用程序,上述的变通方法越来越不必要。C++11突然变得很简单:只需摒弃复制构造函数和赋值操作符。我们的类将如下所示:
class Car {
public:
Car(const Car&) = delete;
void operator=(const Car&) = delete;
Car(): owner() {}
void setOwner(Person *o) { owner = o; }
Person *getOwner() const { return owner; }
private:
Person *owner;
};
需要注意的是,如果你使用支持C++11的编译器将boost::noncopyable mixin与类一同使用,boost::noncopyable的实现也会自动去除上述的成员函数。
任何意外的拷贝行为都会产生更友好的错误信息:
example.cpp:34:7: error: call to deleted constructor of 'Car'
Car anotherSedan = sedan;
^ ~~~~~
example.cpp:10:3: note: 'Car' has been explicitly marked deleted here
Car(const Car&) = delete;
^