目录
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
在C++98中,将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
//C++98
class CopyBan
{
//...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
①设置成私有之后,如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了。
②只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
在C++11中,C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
//C++11
class CopyBan
{
//...
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
//...
};
实现方法:
只能在堆上创建对象,意味着我们需要禁止产生栈对象,只能在堆上创建对象。由于在栈上的对象会自动析构,因此把析构函数私有化。
同时将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
最后提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
//只能在堆上创建对象的类
class HeapOnly
{
public:
//通过静态成员函数,在堆上完成对象的创建
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
void Destory()
{
this->~HeapOnly();
}
private:
~HeapOnly()
{}
//将构造函数和拷贝构造私有
HeapOnly()
{}
HeapOnly(const HeapOnly&) = delete;
};
int main()
{
//创建失败,因为构造函数HeapOnly()私有了
// //这个对象除了会调用构造函数,还会调用析构函数
//HeapOnly hp1;
//HeapOnly* php2 = new HeapOnly;
//static HeapOnly hp3;
//通过调用静态成员函数,创建堆上的对象
HeapOnly* php4 = HeapOnly::CreateObj();
//拷贝失败,因为拷贝构造函数HeapOnly(const HeapOnly&)被删除了
//HeapOnly hp5(*php4);
//delete php4;
php4->Destory();
return 0;
}
产生堆对象的唯一方法是new出来,我们只需把new的重载和delete重载给禁止掉了🆗了。同时我们把构造函数私有化,让用户只能通过特定的方式去创建对象,从而保证了类的安全性和完整性,也能防止外部直接创建该类对象,导致出现不合法的对象。
//只能在栈上创建对象
class StackOnly
{
public:
static StackOnly Createboj()
{
return StackOnly();
}
void Print() const
{
cout << "StackOnly::Print()" << endl;
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly()
{}
};
int main()
{
//new 出来的是堆上的,已经被删除了
//StackOnly sq = new StackOnly();
StackOnly sq1 = StackOnly::Createboj();
StackOnly::Createboj().Print();
sq1.Print();
const StackOnly& so = StackOnly::Createboj();
so.Print();
return 0;
}
子类继承父类的时候,子类中父类部分需要去调用父类的构造函数。
在C++98的方式中,我们可以将构造函数私有化,子类中调不到父类的构造函数。则无法继承。
//C++98
class NonInerit
{
public:
static NonInerit GetInstance()
{
return NonInerit();
}
private:
NonInerit()
{}
};
在C++11中,可以直接使用final关键字修饰类,这个类就不能被继承了。
class NonInerit final
{
//....
};
饿汉模式就是不管将来使不使用,在main函数之前就创建了一个对象。采用静态成员变量方法,在类中声明,在类外定义。这样就能在程序开始之前创建了一个对象。然后构造函数,拷贝和赋值都私有删除,提供特定的获取对象方法。
//饿汉模式
class InfoSingleton
{
public:
//获取对象
static InfoSingleton& GetInstance()
{
return _sins;
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ": " << kv.second << endl;
}
cout << endl;
}
private:
InfoSingleton()
{}
InfoSingleton(const InfoSingleton& info) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
//用于测试
map<string, int> _info;
private:
//声明
static InfoSingleton _sins;
};
//定义
InfoSingleton InfoSingleton::_sins;
int main()
{
//对象在main函数之前就已经被创建出来了。
//因此不能创建对象
//InfoSingleton info1;
//获取对象后使用类的方法
InfoSingleton::GetInstance().Insert("张三", 10000);
//也能使用引用获取对象
InfoSingleton& infos1 = InfoSingleton::GetInstance();
infos1.Insert("李四", 12000);
infos1.Insert("王五", 15000);
//两个打印都是同一个对象的
infos1.Print();
InfoSingleton::GetInstance().Print();
return 0;
}
饿汉模式的缺点是初始化时数据太多,导致启动慢,并且多个单例类有初始化依赖关系,饿汉模式无法控制。
而如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。因此懒汉模式的优点是第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
//懒汉模式:第一次获取单例对象的时候创建对象
//1.对象在main函数之后才会创建,不会影响启动顺序
//2.可以主动控制创建顺序
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
//使用双检查加锁的方式保证线程安全
//在第一次获取单例对象的时候创建对象
if (_psins == nullptr)
{
_smtx.lock();
try
{
if (_psins == nullptr)
{
//new有可能会抛异常
_psins = new InfoSingleton;
}
}
catch (...)//捕获异常
{
_smtx.unlock();//解锁
throw;//重新抛出异常
}
_smtx.unlock();
}
return *_psins;
}
void Insert(string name, int salary)
{
_info[name] = salary;
}
void Print()
{
for (auto kv : _info)
{
cout << kv.first << ": " << kv.second << endl;
}
cout << endl;
}
private:
//将构造函数、拷贝构造和赋值重载私有化或删除
InfoSingleton()
{}
InfoSingleton(const InfoSingleton& info) = delete;
InfoSingleton& operator=(const InfoSingleton& info) = delete;
map<string, int> _info;
private:
//声明
static InfoSingleton* _psins;//单例对象
static mutex _smtx;//锁
};
//定义
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
int main()
{
//不可以
//InfoSingleton info1;
InfoSingleton::GetInstance().Insert("张三", 100000);
InfoSingleton& infos1 = InfoSingleton::GetInstance();
infos1.Insert("李四", 12000);
infos1.Insert("王五", 15000);
infos1.Print();
InfoSingleton::GetInstance().Print();
return 0;
}
我们可以在获取对象的双检查加锁修改一下代码,可以使用RAII锁管理类。
//RAII锁管理类
template<class Lock>
class LockGuard
{
public:
LockGuard(Lock& lk)
:_lk(lk)
{
_lk.lock();
}
~LockGuard()
{
)lk.unlock();
}
private:
Lock& _lk;
};
static InfoSingleton& GetInstance()
{
//使用双检查加锁的方式保证线程安全
//在第一次获取单例对象的时候创建对象
if (_psins == nullptr)
{
//这是我们简单写的
LockGuard<mutex> lock(_smtx);
//这是官方库中的
//std::lock_guard<mutex> lock(_smtx);
if (_psins == nullptr)
{
//new有可能会抛异常
_psins = new InfoSingleton;
}
}
return *_psins;
}