C++98:
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
C++11:在默认成员函数后跟上=delete
,可以让编译器删除掉该默认成员函数,在编译的时候就报错。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
方法1
//方法1
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
//拷贝构造函数的写法两者选其一
HeapOnly(const HeapOnly&);//c98
HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = HeapOnly::CreateObject();//可以
HeapOnly ho2;//err "HeapOnly::HeapOnly()" (已声明 所在行数:14) 不可访问
HeapOnly ho3(*ho);//err C98: "HeapOnly::HeapOnly(const HeapOnly &)" (已声明 所在行数:16) 不可访问 || C11: “HeapOnly::HeapOnly(const HeapOnly &)”: 尝试引用已删除的函数
方法2 把析构函数声明和实现为私有,把拷贝构造函数声明为私有,不实现。提供一个public的销毁对象方法。
class HeapOnly
{
public:
HeapOnly() {}
void destory()
{
this->~HeapOnly();
}
private:
~HeapOnly(){}
//拷贝构造函数的写法两者选其一
HeapOnly(const HeapOnly&);//c98
//HeapOnly(const HeapOnly&) = delete;//c11
};
//调用
HeapOnly* ho1 = new HeapOnly;//可以
HeapOnly ho;//err 无法访问构造函数
方法1
class StackOnly
{
public:
static StackOnly create()
{
return StackOnly();
}
private:
StackOnly(){}
};
//调用
StackOnly s1 = StackOnly::create();//可以
static StackOnly s2;//err "StackOnly::StackOnly()" (已声明 所在行数:13) 不可访问
static StackOnly s3 = StackOnly::create();//这个没办法封死
StackOnly s4;//全局变量err 无法访问构造函数
StackOnly s5 = new StackOnly;//err 无法访问构造函数
如果仅仅是重载operator new和operator delete,都加上delete的话,只能禁掉在堆上创建对象,无法禁掉静态区、全局区等变量的创建。
C++98:
class NonInherit
{
public:
static NonInherit GetInstance() { return NonInherit(); }
private:
NonInherit() {}
};
C++11:
class A final
{
// ....
};
设计模式:迭代器模式、适配器模式(stack、queue)、单例模式、工厂模式、观察者模式等。
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
思路:一个类只能创建一个对象,那就是要把构造函数、拷贝构造、赋值构造函数都私有。
程序启动时就创建一个唯一的实例对象。如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
优点:简单,无线程安全问题。
缺点:类内static数据对象初始化是在main函数之前!如果这个对象初始化时数据过多可能会导致进程启动慢;多个单例类有初始化依赖关系,饿汉模式无法控制,因为多个单例类对象实例启动顺序是不确定的。(比如A和B都是单例类,由于B依赖A,所以要求先初始化A再初始化B,饿汉模式就无法达到目的)
class Singleton
{
public:
static Singleton* getSingleton()
{
return &_instance;
}
private:
// 构造函数私有
Singleton() {};
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton _instance;
};
Singleton Singleton::_instance;//static变量要在类外初始化
int main()
{
Singleton* s = Singleton::getSingleton();
return 0;
}
如果单例对象构造十分耗时或者占用很多资源,如加载插件、 初始化网络连接、读取
文件等,即使该对象在程序运行时不会用到,饿汉模式下该对象也要在程序一开始就进行初始化,即加载缓慢又浪费资源。
所以这种情况使用懒汉模式(延迟加载)更好。
优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控
制。
缺点:复杂,有线程安全问题需要加锁,一般单例对象不需要考虑资源释放(若有需要,还需要添加资源回收的功能),还要考虑new异常导致的无法解锁问题(要么用try catch,要么用luckguard)
template
class LockGuard{
public:
LockGuard(Lock& lk) : _lk(lk)
{ _lk.lock(); }
~LockGuard(){ _lk.unlock(); }
private:
Lock& _lk;//因为锁是不允许拷贝构造的,所以这里要用引用
};
class Singleton
{
public:
static Singleton* getSingleton()
{
//因为单例模式只在第一次调用的时候创建对象,加锁解锁的动作只需要在第一次有,用双检查加锁来实现
if(_pinstance == nullptr){//第1次判断是避免每次都加锁,提高性能
_mtx.lock();
//第2次判断是保证线程安全且只new一次 第一次获取单例对象时创建对象和加锁
try
{
if(_pinstance == nullptr) _pinstance = new Singleton;
}
catch(...)
{
_mtx.unlock();
throw;
}
_mtx.unlock();
}
return _pinstance;
}
// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo(){
if (Singleton::_pinstance) delete Singleton::_pinstance;
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
// 构造函数私有
Singleton() {};
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton* _pinstance;//唯二的变化 单例对象指针
static mutex _mtx;//唯二的变化 互斥锁
};
Singleton* Singleton::_pinstance = nullptr;//static变量要在类外初始化
Singleton::CGarbo Singleton::CGarbo CGarbo;
mutex Singleton::_mtx;
int main()
{
Singleton* s = Singleton::getSingleton();
return 0;
}
全局静态变量的构造函数会在main之前执行,析构函数在main之后执行。
而局部静态变量是在main之后初始化
//该写法是懒汉模式,静态的局部变量在main函数之后被调用才创建初始化的
//c11前对静态局部变量的初始化没有做出相关规定,故无法保证线程安全,c11后作出规定了编译器实现了保证静态局部变量的线程安全
static Singleton& getSingleton()
{
static Singleton inst;//函数体内定义该对象,只会在调用的时候初始化,并且在main函数之后创建
return inst;
}