【C++】特殊类设计

欢迎来到Cefler的博客😁

🕌博客主页:折纸花满衣

🏠个人专栏:题目解析

🌎推荐文章:【LeetCode】winter vacation training


目录

👉🏻设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

🌸 C + + 98 C++98 C++98

将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

cpp 复制代码
class CopyBan
{
    // ...
    
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了 ,就可以不
    能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
    反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

🌸 C + + 11 C++11 C++11

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数

cpp 复制代码
class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

👉🏻设计一个类,只能在堆上创建对象

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
cpp 复制代码
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;
};

👉🏻设计一个类,只能在栈上创建对象

方法:将构造函数私有化,然后设计静态方法创建对象返回即可。

cpp 复制代码
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

cpp 复制代码
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
 static NonInherit GetInstance()
 {
 return NonInherit();
 }
private:
 NonInherit()
 {}
};

🌸 C + + 11 C++11 C++11

final关键字,final修饰类,表示该类不能被继承

cpp 复制代码
class A  final
{
    // ....
};

👉🏻设计一个类,只能创建一个对象(单例模式)

🥱单例模式概念

单例模式是一种创建型设计模式,它保证一个类只有一个实例,且提供全局访问点

在使用单例模式时,我们通过限制类的构造函数和使用一个静态变量来确保只有一个实例。通常情况下,我们会将这个静态变量定义为私有静态成员变量,只能通过类的静态方法来获取这个实例 。这个静态方法通常被称为"获取实例的方法"或"工厂方法"。

单例模式常用于需要全局唯一实例的场景,例如配置信息、日志记录器、数据库连接池等。

单例模式有两种实现模式:

饿汉模式

饿汉模式(Eager Initialization): 在饿汉模式下,单例实例在程序启动时就被创建,因此它是线程安全的。具体实现如下:

cpp 复制代码
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方法时进行实例化。这种实现方式需要考虑线程安全性。以下是一种线程安全的懒汉模式的实现

cpp 复制代码
#include <iostream>
#include <mutex>
#include <thread>
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)是指在多线程编程中需要保证原子性和可见性的一段代码或区域。在同步区域内,只允许一个线程执行代码,其他线程需要等待。

注意 注意 注意 📢

单例模式下,拷贝和赋值函数必须禁掉,那不然就可能会以拷贝构造或赋值的形式诞生多个对象,而不符合单例模式的实例唯一性了。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

相关推荐
神仙别闹7 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE8 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
我们的五年17 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
zwjapple24 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five25 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省27 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱40 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
做人不要太理性44 分钟前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.1 小时前
2、桥接模式
c++·桥接模式
chnming19871 小时前
STL关联式容器之map
开发语言·c++