背景:项目中需要某个类不能被拷贝构造和赋值构造,下面举例说明该场景:
什么时候需要不可拷贝类
考虑一种情况,我们要实现一个含有动态数组成员的类,其中动态数组成员在构造函数中 new 出来,在析构函数中 delete 掉。比如说这样一个矩阵类:
template<typename _T>
class Matrix {
public:int w;int h;_T* data;// 构造函数Matrix(int _w, int _h): w(_w), h(_h){data = new _T[w*h];}// 析构函数~Matrix() {delete [] data;}
}
上面的测试 1 中,我们先构造了 m1
和 m2
两个 Matrix
实例,这意味着他们各自开辟了一块动态内存来存储矩阵数据。然后我们使用 =
将 m2
拷贝给 m1
,这时候 m1
的每个成员(w
,h
,data
)都被各自使用 =
运算符拷贝为和 m2
相同的值。m1.data
是个指针,所以就和 m2.data
指向了同一块的内存。于是这里就会出现两个问题:其一, 发生拷贝前 m1.data
指向的动态内存区在拷贝后不再拥有指向它的有效指针,无法被释放,于是发生了内存泄露;其二,在 copy()
结束后,m1
和 m2
被销毁,各自调用析构函数,由于他们的 data
指向同一块内存,于是发生了双重释放。
测试 2 中也有类似问题。当调用 copy(Matrix<_T> cpy)
时,形参 cpy
拷贝自实参,而 cpy
会在函数结束时销毁,cpy.data
指向的内存被释放,所以实参的矩阵数据也被销毁了——这显然是我们不愿意看见的。同样的,在返回时,ret
随着函数结束而销毁,返回值因为拷贝自 ret
,所以其矩阵数据也被销毁了。
因此,对于像 Matrix
这样的类,我们不希望这种拷贝发生。一个解决办法是重载拷贝函数,每次拷贝就开辟新的动态内存:
Matrix<_T>& operator = (const Matrix<_T>& cpy) {w = cpy.w;h = cpy.h;delete [] data;data = new _T[w*h];memcpy(data, cpy.data, sizeof(_T)*w*h);return *this;
}Matrix(const Matrix<_T>& cpy):w(cpy.w), h(cpy.h) {data = new _T[w*h];memcpy(data, cpy.data, sizeof(_T)*w*h);
}
这样做也有不好的地方。频繁开辟动态内存,当数据量很大时(比如图像处理),对程序性能是有影响的。在接口设计的角度考虑,应该把这种拷贝操作以较明显的形式提供给用户,比如禁用等号拷贝,以直接的函数代替 =
操作:
void copyFrom(const Matrix<_T>& cpy) {w = cpy.w;h = cpy.h;delete [] data;data = new _T[w*h];memcpy(data, cpy.data, sizeof(_T)*w*h);
再禁用构造拷贝,只允许用户以引用传递的办法在自定义函数中使用 Matrix
类。
那么,如何禁止拷贝操作呢?
实现不可拷贝类
使用 boost::noncopyable
Boost 作为 C++ 万金油工具箱,在 <boost/noncopyable.hpp>
下提供了不可拷贝类的实现,使用起来也非常简单,让自己的类继承自 boost::noncopyable
即可:
class Matrix : boost::noncopyable
{// 类实现
}
声明拷贝函数为私有
如果不想用第三方库,自己实现呢?不妨先看一下 Boost 是怎么做的:
private: // emphasize the following members are privatenoncopyable( const noncopyable& );noncopyable& operator=( const noncopyable& );
嗯,****直接把拷贝函数声明为私有****的不就等于禁用了么,so smart!于是:
template<typename _T>
class Matrix
{
private:Matrix(const Matrix<_T>&);Matrix<_T>& operator = (const Matrix<_T>&);
}
C++ 11 下使用 delete 关键字
C++ 11 中为不可拷贝类提供了更简单的实现方法,使用 delete 关键字即可:
template
class Matrix
{
public:
Matrix(const Matrix<_T>&) = *delete*;
Matrix<_T>& operator = (const Matrix<_T>&) = *delete*;
}
关于类似 Matrix
矩阵类的实现,更高级的做法是像智能指针一样封装其内部数据,用内部计数器来确定动态分配的成员是否要释放掉,不过这是另外一个问题了。
boost::noncopyable比较简单, 主要用于单例的情况.
**通常情况下, 要写一个单例类就要在类的声明把它们的构造函数, 赋值函数, 析构函数, 复制构造函数隐藏到private或者protected之中, 每个类都这么做麻烦**.
有noncopyable类, 只要让单例类直接继承noncopyable.
class noncopyable的基本思想是把构造函数和析构函数设置protected权限,这样子类可以调用,但是外面的类不能调用,那么当子类需要定义构造函数的时候不至于通不过编译。但是最关键的是****noncopyable把复制构造函数和复制赋值函数做成了private****,这就意味着除非子类定义自己的copy构造和赋值函数,否则在子类没有定义的情况下,外面的调用者是不能够通过赋值和copy构造等手段来产生一个新的子类对象的。
private: // emphasize the following members are private
noncopyable( const noncopyable& );
const noncopyable& operator=( const noncopyable& );
#ifndef BOOST_NONCOPYABLE_HPP_INCLUDED
#define BOOST_NONCOPYABLE_HPP_INCLUDEDnamespace boost {// Private copy constructor and copy assignment ensure classes derived from
// class noncopyable cannot be copied.// Contributed by Dave Abrahamsnamespace noncopyable_ // protection from unintended ADL
{class noncopyable{protected:noncopyable() {}~noncopyable() {}private: // emphasize the following members are privatenoncopyable( const noncopyable& );const noncopyable& operator=( const noncopyable& );};
}typedef noncopyable_::noncopyable noncopyable;} // namespace boost#endif // BOOST_NONCOPYABLE_HPP_INCLUDED
#include "tfun.h"class myclass: public boost::noncopyable
{
public:myclass(){};myclass(int i){};
};int main()
{myclass cl1();myclass cl2(1);// myclass cl3(cl1); // error// myclass cl4(cl2); // errorreturn 0;
}