【C++】特殊类设计 {不能被拷贝的类;只能在堆上创建的类;只能在栈上创建的类;不能被继承的类;单例模式:懒汉模式,饿汉模式}

一、不能被拷贝的类

设计思路:

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

C++98方案:
将拷贝构造与赋值重载只声明不定义,并且将其访问权限设置为私有即可。

class CopyBan
{
	// ...
private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//...
};

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了。

  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

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

class CopyBan
{
	// ...
	CopyBan(const CopyBan&)=delete;
	CopyBan& operator=(const CopyBan&)=delete;
	//...
};

二、只能在堆上创建的类

思路一:将构造、拷贝构造函数私有

  1. 将类的构造、拷贝构造声明成私有。
  2. 提供一个静态的成员函数,在该静态成员函数中使用new申请堆空间并调用构造函数完成堆对象的初始化,最后返回该对象的指针。
class HeapOnly
{
    int _val;
    // 把构造和拷贝构造设置成私有
    HeapOnly(int val = 0)
        : _val(val)
    {
    }
    // 一定要把拷贝构造也设为私有
    HeapOnly(const HeapOnly &obj);

public:
    // 提供一个静态的成员函数,使用new申请堆空间并调用构造函数完成堆对象的创建。
    static HeapOnly *CreateObj(int val = 0)
    {
        return new HeapOnly(val);
    }
};

int main()
{
    // HeapOnly obj;
    HeapOnly *pobj1 = HeapOnly::CreateObj(10);
    // HeapOnly obj(*pobj1);

    return 0;
}

思路二:将析构函数私有

编译器在为类对象分配栈空间时,会先检查类的构造和析构函数的访问性。由于栈的创建和释放都需要由系统完成的,所以若是无法调用构造或者析构函数,自然会报错。如果类的析构函数是私有的,则编译器将报错。

当然为了我们能够释放动态创建的对象,我们必须提供一个公有函数,该函数的唯一功能就是删除堆对象。

  1. 将类的析构函数声明成私有。
  2. 提供一个公有的成员函数,执行delete this调用析构函数清理对象资源并释放堆空间。
class HeapOnly
{
    int _val;
    // 把析构设置成私有
    ~HeapOnly()
    {
        cout << "~HeapOnly()" << endl;
    }

public:
    HeapOnly(int val = 0)
        : _val(val)
    {
    }
	// 提供一个公有的成员函数,执行delete this调用析构函数清理对象资源并释放堆空间
    void DestroyObj()
    {
        delete this;
    }
};

int main()
{
    // HeapOnly obj;
    HeapOnly *pobj = new HeapOnly(10);
    // HeapOnly obj(*pobj);
    // delete pobj;
    pobj->DestroyObj();

    return 0;
}

三、只能在栈上创建的类

思路:重载operator new

我们还可以将new操作符重载并设置为私有访问。

class StackOnly
{
    int _val;

    void* operator new(size_t t);
public:
    StackOnly(int val = 0)
        : _val(val)
    {
    }

    StackOnly(const StackOnly &obj)
        : _val(obj._val)
    {
    }
};

int main()
{
    StackOnly obj(10);
    StackOnly obj1(obj);
    // StackOnly *pobj = new StackOnly(10);
    // StackOnly *pobj1 = new StackOnly(obj);

    return 0;
}

四、不能被继承的类

C++98方案:将构造函数私有

派生类中调不到基类的构造函数,则无法继承。

class NonInherit
{
public:
	static NonInherit CreatObj()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};

C++11方案:final关键字

final修饰类,表示该类不能被继承。

class A final
{
	// ....
};

五、单例模式

5.1 设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的代码设计经验总结。

使用设计模式的目的:
为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

常用的设计模式:

  1. 适配器模式:对已有的类进行适配包装形成具有全新功能和性质的类,如:栈、队列、优先级队列、function包装器。
  2. 迭代器模式:几乎所有容器通用的遍历访问方式,可以封装隐藏容器的底层结构,以类似指针的使用方式访问容器中的数据。如:数组(vector)、链表(list)、哈希表(unordered_map)、树(map)的迭代器。
  3. 单例模式:接下来的内容
  4. 工厂模式:工厂模式是一种创建对象的设计模式,它通过定义一个工厂类来封装对象的创建过程,并通过调用工厂类的方法来创建对象,从而将对象的创建与使用分离。
  5. 观察者模式:观察者模式是一种对象间的一对多依赖关系,当一个对象的状态发生变化时,它的所有依赖者都会得到通知并自动更新。

单例模式:

  • 一个类只能创建一个对象,即单例模式。该模式可以保证系统中(进程中)该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块(线程及函数)共享。
  • 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
  • 比如空间配置器一般也是单例模式。
  • 单例模式有两种实现模式:饿汉模式和懒汉模式。

5.2 饿汉模式

所谓饿汉模式,就是说不管你将来用不用,程序启动时(main函数之前)就创建一个唯一的实例对象。

方法一:在堆区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态指针并在类外使用new创建单例,提供一个访问单例的全局访问点;
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 提供一个用于获取全局访问点(静态指针)的静态成员函数;
  5. 包含一个静态的内部类对象,该对象析构时会顺便析构单例,自动释放。
class Singleton
{
    // 成员变量
    vector<string> _dir;
    // 该类的静态指针,提供一个访问单例的全局访问点
    static Singleton *s_ins;
    // 互斥锁成员,保证多线程互斥访问该单例
    mutex s_mtx;
    // 静态的内部类对象,该对象析构时会顺便析构单例,自动释放
    struct GC
    {
        ~GC()
        {
            if (s_ins != nullptr)
            {
                delete s_ins;
                s_ins = nullptr;
            }
        }
    };
    static GC s_gc;
    // 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
    Singleton()
    {
        cout << "Singleton()" << endl;
    };
    Singleton(const Singleton &st);
    ~Singleton()
    {
        // 单例对象的析构一般会做一些持久化操作(数据落盘)
        // ......
        cout << "~Singleton()" << endl;
    }

public:
    // 提供一个静态成员函数,用于获取全局访问点(静态指针)
    static Singleton *GetInstance()
    {
        return s_ins;
    }

    void Add(const string &name)
    {
        s_mtx.lock();
        _dir.push_back(name);
        s_mtx.unlock();
    }

    void Print()
    {
        s_mtx.lock();
        for (auto &name : _dir)
        {
            cout << name << endl;
        }
        s_mtx.unlock();
    }
};

// 程序启动时(main函数之前)创建
Singleton *Singleton::s_ins = new Singleton;
Singleton::GC Singleton::s_gc;

int main()
{
    // 系统中该类只有一个实例,不允许通过任何方式实例化
    // Singleton st;
    // static Singleton st1;
    // Singleton* pst = new Singleton;
    //  Singleton st(*(Singleton::GetInstance()));

    // 单线程场景
    // Singleton::GetInstance()->Add("张三");
    // Singleton::GetInstance()->Add("李四");
    // Singleton::GetInstance()->Add("王五");

    // Singleton::GetInstance()->Print();

    // 多线程场景
    int n = 6;
    srand((unsigned int)time(nullptr));
    thread t1([n]() mutable
              {
        while(n--)
        {
            Singleton::GetInstance()->Add("线程1:" + to_string(rand()));
            this_thread::sleep_for(chrono::milliseconds(10));
        } });

    thread t2([n]() mutable
              {
        while(n--)
        {
            Singleton::GetInstance()->Add("线程2:" + to_string(rand()));
            this_thread::sleep_for(chrono::milliseconds(10));
        } });

    t1.join();
    t2.join();
    Singleton::GetInstance()->Print();
}

运行结果(多线程场景):

在这里插入图片描述


方法二:在静态区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态对象并在类外定义,提供一个访问单例的全局访问点;
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 提供一个用于获取全局访问点(静态对象的引用)的静态成员函数;
  5. 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
// 饿汉模式2
class Singleton
{
    // 成员变量
    vector<string> _dir;
    // 该类的静态对象,提供一个访问单例的全局访问点
    static Singleton s_ins;
    // 互斥锁成员,保证多线程互斥访问该单例
    mutex s_mtx;
    // 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
    Singleton()
    {
        cout << "Singleton()" << endl;
    };
    Singleton(const Singleton &st);
    // 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
    ~Singleton()
    {
        // 单例对象的析构一般会做一些持久化操作(数据落盘)
        // ......
        cout << "~Singleton()" << endl;
    }

public:
    // 提供一个静态成员函数,用于获取全局访问点(静态对象的引用)
    static Singleton &GetInstance()
    {
        return s_ins;
    }

    void Add(const string &name)
    {
        s_mtx.lock();
        _dir.push_back(name);
        s_mtx.unlock();
    }

    void Print()
    {
        s_mtx.lock();
        for (auto &name : _dir)
        {
            cout << name << endl;
        }
        s_mtx.unlock();
    }  
};

// 程序启动时(main函数之前)创建
Singleton Singleton::s_ins;

运行结果:同上

饿汉模式的缺点:

  1. 由于单例对象是在main函数之前创建的,如果单例对象很大,很复杂,其创建和初始化所占用的时间较多。会拖慢程序的启动速度。
  2. 如果当前进程暂时不需要使用该单例对象,而饿汉模式在启动时创建单例占用了空间和时间资源。
  3. 如果具有依赖关系的两个单例都是饿汉模式,需要先创建单例1再创建单例2。饿汉模式无法控制其创建和初始化顺序。

提示:饿汉模式的全局访问点除了定义静态指针还可以直接定义成静态对象。如果是静态对象,进程在退出时会自动调用其析构函数。


5.3 懒汉模式

如果单例对象的构造十分耗时或者占用很多资源,比如加载插件、 初始化网络连接、读取文件等等。而且有可能程序运行时不会用到该对象,如果也在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

所谓懒汉模式,就是在任意程序模块第一次访问单例时实例化对象。

方法一:在堆区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态指针并在类外初始化为nullptr,提供一个访问单例的全局访问点;
  3. 包含一个静态互斥锁并在类外定义,保证多线程互斥地创建和访问该单例;
  4. 提供一个静态成员函数,用于首次调用创建单例(注意双检查加锁)和获取全局访问点(静态指针);
  5. 包含一个静态的内部类对象,该对象析构时会顺便析构单例,自动释放。
// 懒汉模式
class Singleton
{
    // 成员变量
    vector<string> _dir;
    // 该类的静态指针,提供一个访问单例的全局访问点
    static Singleton *s_ins;
    // 静态互斥锁,保证多线程互斥地创建和访问该单例
    static mutex s_mtx;
    // 静态的内部类对象,该对象析构时会顺便析构单例,自动释放
    struct GC
    {
        ~GC()
        {
            if (s_ins != nullptr)
            {
                delete s_ins;
                s_ins = nullptr;
            }
        }
    };
    static GC gc;
    // 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
    Singleton()
    {
        cout << "Singleton()" << endl;
    };
    Singleton(const Singleton &st);
	~Singleton()
    {
        // 单例对象的析构一般会做一些持久化操作(数据落盘)
        // ......
        cout << "~Singleton()" << endl;
    }
public:
    static Singleton *GetInstance()
    {
        // 懒汉模式:在第一次访问实例时创建
        // 双检查加锁
        if (s_ins == nullptr) // 第一道检查:提高效率,不需要每次获取单例都加锁解锁
        {
            s_mtx.lock();
            if (s_ins == nullptr) // 第二道检查:保证线程安全和只new一次
            {
                s_ins = new Singleton;
            }
            s_mtx.unlock();
        }
        return s_ins;
    }

    void Add(const string &name)
    {
        s_mtx.lock();
        _dir.push_back(name);
        s_mtx.unlock();
    }

    void Print()
    {
        s_mtx.lock();
        for (auto &name : _dir)
        {
            cout << name << endl;
        }
        s_mtx.unlock();
    }
    
    // 一般单例对象的生命周期随进程,系统会在进程退出时释放其内存,不需要中途析构单例对象
    // 不过在一些特殊场景下,可能需要进行显示手动释放
    static void DelInstance()
    {
        s_mtx.lock();
        if (s_ins != nullptr)
        {
            delete s_ins;
            s_ins = nullptr;
        }
        s_mtx.unlock();
    }
};

// 静态成员要在类外定义
Singleton *Singleton::s_ins = nullptr;
mutex Singleton::s_mtx;
Singleton::GC Singleton::gc;

运行结果(多线程场景):

在这里插入图片描述


方法二:在静态区创建单例(C++11)

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 提供一个静态成员函数,用于首次调用创建单例(创建静态局部对象)和获取全局访问点(静态对象的指针);
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
// 懒汉模式2
class Singleton
{
    // 成员变量
    vector<string> _dir;
    // 互斥锁成员,保证多线程互斥访问该单例
    mutex s_mtx;
    // 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
    Singleton()
    {
        cout << "Singleton()" << endl;
    };
    Singleton(const Singleton &st);
    ~Singleton()
    {
        // 单例对象的析构一般会做一些持久化操作(数据落盘)
        // ......
        cout << "~Singleton()" << endl;
    }

public:
    static Singleton *GetInstance()
    {
        // C++11之前,这里不能保证初始化静态对象的线程安全问题
        // C++11之后,这里可以保证初始化静态对象的线程安全问题
        static Singleton s_ins; //首次调用时创建局部静态对象
        return &s_ins;
    }

    void Add(const string &name)
    {
        s_mtx.lock();
        _dir.push_back(name);
        s_mtx.unlock();
    }

    void Print()
    {
        s_mtx.lock();
        for (auto &name : _dir)
        {
            cout << name << endl;
        }
        s_mtx.unlock();
    }
};

运行结果:同上

懒汉模式模式完美解决了饿汉模式的问题,就是相对复杂一些。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/180814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虚拟机VMware客户机隔离灰色如何解决||实现本机复制粘贴到虚拟机

前言&#xff1a;本次镜像为win10&#xff0c;其他操作系统也欢迎尝试 现象&#xff1a;虚拟机设置选项不可编辑&#xff0c;且是否勾选都无法实现复制粘贴 可能存在的问题解决方案 Q1&#xff1a;未安装虚拟机工具&#xff1a;VMware Tools A1&#xff1a;安装工具&#xff…

解决“使用 CNKI 保存时发生错误。改为尝试用 DOI 保存。”【Bug Killed】

文章目录 简介解决办法跟新本地Zotero中茉莉花插件的非官方维护中文翻译器更新网页插件Zetero Connector中的Transtors 结语参考资料 简介 使用Chrome ➕ Zotero Connector保存中国知网&#xff08;CNKI&#xff09;的参考文献到本地的Zotero时无法正常保存&#xff0c;出现使…

ubuntu22.04在线安装redis,可选择版本

安装脚本7.0.5版本 在线安装脚本&#xff0c;默认版本号是7.0.5&#xff0c;可以根据需要选择需要的版本进行下载编译安装 sudo apt-get install gcc -y sudo apt-get install pkg-config -y sudo apt-get install build-essential -y#安装redis rm -rf ./tmp.log systemctl …

电脑键盘推荐

一、键盘分类 &#xff08;1&#xff09;键位个数 目前有75&#xff0c;84&#xff0c;87&#xff0c;98&#xff0c;104&#xff0c;108的。 &#xff08;2&#xff09;薄膜键盘和机械键盘 薄膜键盘就是大多数办公室常见的键盘&#xff0c;主要打一个便宜&#xff0c;耐造…

《人月神话》读书笔记

文章目录 一、书名和作者二、书籍概览2.1 主要论点和结构2.2 目标读者和应用场景 三、核心观点与主题3.1 人员组织管理主题3.2 项目时间进度管理主题3.3 项目成本风险管理主题3.4 软件工程内在本质 四、亮点与启发4.1 最有影响的观点4.2 对个人专业发展的启示 五、批评与局限性…

SQLite3 数据库学习(五):Qt 数据库高级操作

参考引用 SQLite 权威指南&#xff08;第二版&#xff09;SQLite3 入门 1. Qt 数据库密码加密 MD5 加密在线工具 1.1 加密流程 加密后的密码都是不可逆的 1.2 代码实现 loginsqlite.h #ifndef LOGINSQLITE_H #define LOGINSQLITE_H#include <QWidget> #include <Q…

博士研究生不会编程,也没有使用过Python,是否很失败

首先&#xff0c;对于博士研究生来说&#xff0c;虽然在学习和科研的过程中会涉猎到大量的专业知识&#xff0c;但是同样也会错过很多知识&#xff0c;对于非计算机相关专业的博士研究生来说&#xff0c;没有使用过Python&#xff0c;或者说编程能力比较弱也是比较正常的情况&a…

使用whisper实现语音转文本

项目地址&#xff1a;GitHub - openai/whisper: Robust Speech Recognition via Large-Scale Weak Supervision 1、需要py3.8环境 conda activate p38 2、安装 pip install -U openai-whisper 3、下载项目 pip install githttps://github.com/openai/whisper.git 4、安装…

Class文件转Java文件

目录 1、下载一个反编译工具2、在文件夹下打开命令窗口3、在此目录下随意建一个文件夹4、在打开的命令窗口输入命令5、返回解压目录下 1、下载一个反编译工具 下载链接&#xff1a;https://varaneckas.com/jad/ 下载的是第一个 下载后放至任意目录下解压即可 2、在文件夹下打…

E-R图与关系模式

1. E-R模型 英文全称&#xff1a;Entity-relationship model&#xff0c;即实体关系模型 把现实世界的 实体模型通过建模转换为信息世界的概念模型&#xff0c;这个概念模型就是E-R模型 2. 数据库设计流程 一般设计数据库分为三个步骤 把现实世界的实体模型&#xff0c;通…

手把手教你如何提交App备案

手把手教你如何提交App备案 随着工信部出台了《工业和信息化部关于开展移动互联网应用程序备案工作的通知》对于我司所使用的到的移动应用APP就需要做app备案&#xff0c;今天用游戏app手把手教你如何提交App备案。 基本操作流程 运营、市场 提供需要备案的APP名称、主体、A…

市场是变化的?这种悖论fpmarkets澳福一秒打破

你是不是始终认为市场是经常变化的&#xff0c;其实这是不对的&#xff0c;这种认识fpmarkets澳福今天一秒打破。 市场经常变化吗?众多投资者无需过多思考&#xff0c;就认为答案是肯定的。因为无论是在互联网的哪个角落&#xff0c;都可以看到这样的信息。即使我们没有深入研…

mysql查询统计最近12个月的数据

项目场景&#xff1a; mysql查询统计最近12个月的数据&#xff0c;按每个月纵向展示&#xff0c;效果图 sql语句 注意&#xff1a;count( v.uuid ) 这里的是被统计那张表的id SELECT m.month,count( v.uuid ) AS total FROM (SELECT DATE_FORMAT(( CURDATE()), %Y-%m ) AS mon…

【实用】mysql配置 及将线上数据导入本地 问题解决及记录

[ERR] 1292 - Incorrect datetime value: ‘0000-00-0000:00:00‘ for column ‘BIRTH_DATE‘ at row 1 此问题是mysql当前配置不支持日期为空&#xff0c;或者为‘0000-00-0000:00:00‘得情况 1、直接在数据库执行 # 修改全局 set global.sql_mode ONLY_FULL_GROUP_BY,STR…

vue2生命周期

前言 vue的生命周期其实可以分为两块,一个是vue实例的生命周期,一个是组件的生命周期。 vue实例的生命周期方法共有4个:$mout,$forceUpdate,$nextTick,$destroy vue组件的生命周期钩子共有8个:beforeCreate,created,beforeMount,mounted,beforeUpdate, updated,beforeDestr…

Element UI的Tabs 标签页位置导航栏去除线条

在实际开发中&#xff0c;我们调整了相关样式&#xff0c;导致导航栏的相关样式跟随不上&#xff0c;如下图所示&#xff1a; 因为我跳转了前边文字的样式并以在导航栏添加了相关头像&#xff0c;导致右边的线条定位出现问题&#xff0c;我在想&#xff0c;要不我继续调整右边…

抖音汽车租赁小程序技术指南:开发高效便捷的租赁系统

为了更好地满足用户需求&#xff0c;抖音汽车租赁小程序成为一个备受关注的技术解决方案。本文将深入探讨开发高效便捷的汽车租赁系统所需的技术要点&#xff0c;为开发者提供一份实用的技术指南。 小程序架构选择 在搭建抖音汽车租赁小程序时&#xff0c;选择合适的小程序架构…

TIDB拓扑结构

TiDB Server&#xff1a;SQL层&#xff0c;负责接受客户端的连接&#xff0c;执行SQL解析和优化&#xff0c;最终生成分布式执行计划。TiDB Server为无状态的&#xff0c;可增加节点负载均衡。 PD (Placement Driver) Server&#xff1a;整个TiDB集群的元信息管理模块&#xf…

露营管理系统预约小程序效果如何

旅游经济已经复苏&#xff0c;并且市场规模增速加快&#xff0c;近一年来远途/周边游客户增多&#xff0c;不少旅游景区在节假日常常面对客流爆满现象。同时露营作为近几年突然火热的项目&#xff0c;其需求也是日渐上升。 然而在高需求的同时&#xff0c;我们也看到露营经营痛…

修改QtCreator/QDesigner的对象指示器高亮颜色

一、前言 QtCreator的设计中&#xff0c;高亮颜色太接近了&#xff0c;在左边点一个对象后&#xff0c;很难在右边对上&#xff0c;体验极差。 二、解决方案 创建一份style.qss&#xff0c;写入以下的样式&#xff1a; /* for QtCreator */ QDockWidget #ObjectInspector …