欢迎来到Cefler的博客
博客主页:折纸花满衣
个人专栏:题目解析
推荐文章:【LeetCode】winter vacation training
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C + + 98 C++98 C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
C + + 11 C++11 C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
实现方式:
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
// C++98
// 1.只声明,不实现。因为实现可能会很麻烦,而你本身不需要
// 2.声明成私有
HeapOnly(const HeapOnly&);
// or
// C++11
HeapOnly(const HeapOnly&) = delete;
};
方法:将构造函数私有化,然后设计静态方法创建对象返回即可。
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉
// StackOnly obj = StackOnly::CreateObj();
// StackOnly* ptr3 = new StackOnly(obj);
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
只能在栈上或堆上创建对象采用的方法都是类似的,就是将构造函数私有化,自己创建一个静态成员函数,也就是说该对象如何被创建出来取决于这个静态成员函数内部如何进行对象创建。
C + + 98 C++98 C++98
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C + + 11 C++11 C++11
final关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
单例模式概念
单例模式是一种创建型设计模式,它保证一个类只有一个实例,且提供全局访问点。
在使用单例模式时,我们通过限制类的构造函数和使用一个静态变量来确保只有一个实例。通常情况下,我们会将这个静态变量定义为私有静态成员变量,只能通过类的静态方法来获取这个实例。这个静态方法通常被称为“获取实例的方法
”或“工厂方法
”。
单例模式常用于需要全局唯一实例的场景,例如配置信息、日志记录器、数据库连接池等。
单例模式有两种实现模式:
饿汉模式(Eager Initialization): 在饿汉模式下,单例实例在程序启动时就被创建,因此它是线程安全的。具体实现如下:
class Singleton {
private:
static Singleton instance; // 在类加载时创建实例
Singleton() {} // 私有构造函数
public:
static Singleton& getInstance() {
return instance;
}
// 其他方法...
};
Singleton Singleton::instance; // 类加载时创建实例
// 使用示例
int main() {
Singleton& singleton = Singleton::getInstance();
//...
return 0;
}
在上述代码中,我们将单例实例定义为一个私有的静态成员变量instance,并且在其声明时直接初始化为Singleton类的实例。通过公共的静态方法getInstance来获取该实例。由于静态成员变量在类加载时就被初始化,所以在多线程环境下也能保证只有一个实例被创建。
饿汉模式的优点是实现简单,线程安全性可靠
,且在调用getInstance方法时无需进行额外的同步操作。然而,它的缺点是无论是否真正使用该实例,都会在程序启动时创建实例,可能会导致资源浪费,可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
懒汉模式(Lazy Initialization): 在懒汉模式下,单例实例在第一次使用时才被创建,即在调用getInstance方法时进行实例化。这种实现方式需要考虑线程安全性。以下是一种线程安全的懒汉模式的实现
#include
#include
#include
using namespace std;
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx;
Singleton() {} // 私有构造函数
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx); // 进入同步区域
if (instance == nullptr) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
//防拷贝和赋值
Singleton(const Singleton&)=delete;
Singleton& operator=(const Singleton&)=delete;
// 其他方法...
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
// 使用示例
int main() {
Singleton* singleton = Singleton::getInstance();
//...
return 0;
}
在上述代码中,我们将单例实例定义为一个私有的静态指针成员变量instance。在第一次调用getInstance方法时,会进行线程安全的实例化操作。通过双重检查锁定(double-checked locking)的方式,在加锁前后都对instance进行了检查,以避免多个线程同时创建实例。
懒汉模式的优点是延迟实例化,减少了启动时的资源消耗
。然而,它的缺点是需要额外的同步操作,可能会影响性能。另外,在某些编译器下,双重检查锁定的方式并不能保证线程安全,需要使用特定的内存屏障指令来保证正确性。
双重检查锁定是什么?
双重检查锁定(Double-Checked Locking)是一种常用的懒汉式线程安全单例模式实现方式,它可以避免多个线程同时创建实例,提高了程序的性能和效率。
在双重检查锁定的实现中,首先进行一次instance == nullptr的检查,如果不为空,则直接返回已经创建好的实例;否则进入同步区域,对实例进行初始化。而在同步区域内部还需要再次进行一次instance == nullptr的检查,以确保只有一个线程创建实例。这里的双重检查就是指两次判断instance是否为nullptr。
同步区域
(Synchronization Region)是指在多线程编程中需要保证原子性和可见性的一段代码或区域。在同步区域内,只允许一个线程执行代码,其他线程需要等待。
注意 注意 注意
单例模式下,拷贝和赋值函数必须禁掉,那不然就可能会以拷贝构造或赋值的形式诞生多个对象,而不符合单例模式的实例唯一性了。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长