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

注意 注意 注意 📢

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


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

相关推荐
Buleall2 分钟前
期末考学C
java·开发语言
重生之绝世牛码4 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行10 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
Algorithm157620 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
shinelord明30 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
Monly2136 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu37 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202137 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
小俊俊的博客38 分钟前
海康RGBD相机使用C++和Opencv采集图像记录
c++·opencv·海康·rgbd相机
7yewh40 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux