拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
有以下两种方法,分别是C++98和C++11;
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可,如下:
class CopyBan
{
public:
CopyBan()
{}
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
};
原因:
C++11扩展 delete 的用法,delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上 =delete ,表示让编译器删除掉该默认成员函数,如下:
class CopyBan
{
public:
CopyBan()
{}
private:
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
};
将析构函数私有化可以保证不能直接创建对象,因为不能直接调用析构函数,所以只能使用 new 在堆上申请空间。那么问题来了,该怎么释放空间呢?很简单,我们直接提供一个接口,释放 this 指针即可。如下:
class HeapOnly
{
public:
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
};
我们测试一下看看:
int main()
{
HeapOnly* hp = new HeapOnly;
hp->Destroy();
return 0;
}
如下:
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
创建对象的方法:
int main()
{
HeapOnly* hp = HeapOnly::CreateObj();
return 0;
}
思路还是和上面的类似,首先我们先把构造函数私有化,并提供一个返回在栈上创建对象的接口,如下:
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly obj;
return obj;
}
private:
StackOnly()
{}
};
但是这时候还存在另一个问题,还没有完全把 new 禁掉,例如以下代码还是可以通过的:
int main()
{
StackOnly st1 = StackOnly::CreateObj();
StackOnly* st2 = new StackOnly(st1);
return 0;
}
那么如果我们把拷贝构造也禁掉呢?在这里是不行的,因为我们返回栈上的对象的接口 CreateObj()
是传值返回,需要拷贝构造函数。所以另外一个方法就是在类里面写一个 operator new
并禁掉。为什么这样可以呢?其实,如果我们使用 new,默认调的是全局的 new,但是如果我们在类中写一个 operator new
,那么当我们 new 一个该类的对象时,会优先调用我们自己写的 operator new
,所以我们只需要把自己写的 operator new
禁掉即可,如下:
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly obj;
return obj;
}
// 实现类专属的 operator new
void* operator new(size_t size) = delete;
private:
StackOnly()
{}
};
C++98 中构造函数私有化,派生类中调不到基类的构造函数,则无法继承,如下:
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
final 关键字,final 修饰类,表示该类不能被继承。如下:
class NonInherit final
{
public:
NonInherit()
{}
private:
// ...
};
单例模式就是,设计一个只能创建一个对象的类。首先我们先了解一下设计模式。
设计模式:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
饿汉模式就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
首先我们必须将构造函数、拷贝构造和赋值重载私有化。饿汉模式是在程序启动时就创建并初始化一个唯一的对象,所以我们可以使用一个全局静态变量,全局变量是进入 main 函数之前就完成初始化的,所以设为全局变量;而静态是为了能在私有化构造函数的类中创建对象,我们在类和对象部分也讲过,static 的类成员不算该类的成员,静态成员变量属于所有类的对象,属于整个类,即属于整个 Singleton 类。 我们将 GetInstance()
函数也设为静态成员函数,因为在类外只需要突破类域就能访问该函数。饿汉模式的单例模式设计如下:
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_inst;
}
private:
static Singleton _inst; // 声明
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 定义
Singleton Singleton::_inst;
我们也可以返回它的引用,并写一个 Print()
函数进行测试:
class Singleton
{
public:
static Singleton& GetInstance()
{
return _inst;
}
void Print()
{
cout << "void Print()" << endl;
}
private:
static Singleton _inst; // 声明
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 定义
Singleton Singleton::_inst;
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。因为在程序启动前需要进行初始化,如果需要初始化的资源很多,就会降低程序的启动速度。
如果单例对象构造十分耗时或者占用很多资源,比如加载插件, 初始化网络连接,读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。懒汉模式就是在我们需要使用时,才给我们创建对象。
懒汉模式不用在程序启动时就准备好对象,所以我们只需要初始化一个空指针即可,因为空指针不费资源,初始化也很快。在用户调用 GetInstance()
函数时,说明需要创建对象,我们再在函数内部判断是否已经初始化过该对象,即判断 _inst 是否为空,如果为空则 new 一个对象给它并返回,否则直接返回。这样就完成了单例模式的懒汉模式,代码如下:
class Singleton
{
public:
static Singleton* GetInstance()
{
if (_inst == nullptr)
{
_inst = new Singleton;
}
return _inst;
}
private:
static Singleton* _inst;
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton* Singleton::_inst = nullptr;
注意,new 的懒汉对象一般不需要释放,进程正常结束会释放资源;但是如果需要做一些动作,比如持久化(持久化:要求把数据写到文件),那么可以利用 gc 类 static 对象搞定。如下代码:
class Singleton
{
public:
static Singleton* GetInstance()
{
if (_inst == nullptr)
{
_inst = new Singleton;
}
return _inst;
}
// 使用 delete 调用析构函数
static void DelInstance()
{
if (_inst)
{
delete _inst;
_inst = nullptr;
}
}
private:
static Singleton* _inst;
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton()
{
cout << "持久化动作..." << endl;
}
class gc
{
public:
~gc()
{
DelInstance();
}
};
// 该对象会在 main 函数结束后自动调用析构函数
static gc _gc;
};
Singleton* Singleton::_inst = nullptr;
Singleton::gc Singleton::_gc;
上述代码中,_gc 对象在 main 函数结束后会自动调用它自己的析构函数,所以我们在它的析构函数调用 DelInstance()
函数,而 DelInstance()
函数是 Singleton 类的一个静态成员函数,我们在 DelInstance()
函数中使用 delete _inst,使它调用 Singleton 类的析构函数,这样我们就可以在析构函数里面做持久化的动作。
有关懒汉模式还有线程安全问题需要解决,我们后面再解决…