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

news2024/11/18 23:41:45

一、不能被拷贝的类

设计思路:

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

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();
    }
};

运行结果:同上

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重生奇迹mu迹辅助什么好

主流辅助一号选手&#xff1a;弓箭手 智弓作为最老、最有资历的辅助职业&#xff0c;一直都是各类玩家的首要选择。因为智力MM提供的辅助能力都是最基础、最有效、最直观的辅助。能够减少玩家对于装备的渴求度&#xff0c;直接提升人物的攻防&#xff0c;大大降低了玩家升级打…

当当网获得dangdang商品详情商品列表API 测试请求入口

item_get-获得dangdang商品详情 获取商品详情 item_search-按关键字搜索dangdang商品 获取商品列表 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请…

单片机调试技巧--栈回溯

在启动文件中修改 IMPORT rt_hw_hard_fault_exceptionEXPORT HardFault_Handler HardFault_Handler PROC; get current contextTST lr, #0x04 ; if(!EXC_RETURN[2])ITE EQMRSEQ r0, msp ; [2]0 > Z1, get fault context from h…

强化学习------贝尔曼方程

目录 前言基础知识马尔可夫决策过程 (Markov decision process, MDP)回报(Return)折扣回报(Discounted Return) State Value&#xff08;状态价值函数&#xff09;贝尔曼方程的推导贝尔曼方程的矩阵形式Action Value&#xff08;动作价值函数&#xff09;贝尔曼最优公式 前言 …

Python——练习2

Python 练习一练习二练习三 练习一 (回文素数)回文素数是指一个数既是素数又是回文数。例如&#xff0c;131 既是素数也是回文数。数字313和717都是如此。编写程序显示前 100 个回文素数。每行显示10个数字&#xff0c;并且准确对齐如下所示。 2 3 5 7 11 …

网络安全等级保护收费标准?

不同省份价格会略有不同&#xff0c;二级等保一般不低于5万元;三级等保不低于9万元&#xff0c;个别省份也可能7万也能办理&#xff0c;根据企业实际情况和省市选定的代理机构确定。 等级保护二级? 第二级等保是指信息系统受到破坏后&#xff0c;会对公民、法人和其他组织的合…

pip安装python包到指定python版本下

python -m pip install 包名1.命令行进入到指定python安装目录。比如我电脑上有python3.8也有python3.9。准备给python3.9安装指定的包

企业软件定制开发有哪些优势?|app小程序网站搭建

企业软件定制开发有哪些优势&#xff1f;|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 首先&#xff0c;企业软件定制开发可以满足企业独特需求。每个企业都有自己独…

redis-cluster集群

1.redis-cluster集群 redis3.0引入的分布式存储方案 集群由多个node节点组成&#xff0c;redis数据分布在这些节点之中。 在集群之中分为主节点和从节点 集群模式当中&#xff0c;主从一一对应&#xff0c;数据写入和读取与主从模式一样&#xff0c;主负责写&#xff0c;从…

ui5使用echart

相关的代码已经发布到github上。 展示下相关的实现功能 1、柱状图-1 2、柱状图-2 3.折线图 4.饼状图 如何使用&#xff1a; 使用git clone项目到本地 git clone https://github.com/linhuang0405/com.joker.Zechart找到index.html。在vscode里右键选择Open with Live Serve…

数字化转型过程中面临最大的问题是什么?如何借助数字化工具实现快速转型?

在科技快速发展的时代&#xff0c;数字化转型已经成为企业的重要战略。当企业努力适应数字化时代并取得成功时&#xff0c;他们可能会面临各种必须有效应对的挑战。   数字化转型不仅仅是将新技术应用到企业的运营中&#xff0c;还需要对企业的运营方式、与客户的互动方式和价…

数据库数据恢复—SQLserver数据库中勒索病毒被加密的数据恢复案例

SQLserver数据库数据恢复环境&故障&#xff1a; 一台服务器上的SQLserver数据库被勒索病毒加密&#xff0c;无法正常使用。该服务器上部署有多个SQLserver数据库&#xff0c;其中有2个数据库及备份文件被加密&#xff0c;文件名被篡改&#xff0c;数据库无法使用。 SQL se…

智能优化算法 | Matlab实现金豺优化算法(GJO)(内含完整源码)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 智能优化算法 | Matlab实现金豺优化算法(GJO)(内含完整源码) 源码设计 %%clear clc close SearchAgents_no=30; % Number of search agents Max_iteration=1000

2023年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 小猫的程序如图所示,积木块的颜色与球的颜色一致。点击绿旗执行程序后,下列说法正确的是?( ) A:小猫一直在左右移动,嘴里一直说着“抓到了”。 B:小猫会碰到球,然后停止。…

某资产管理机构: IAST提升安全水平,保障资产管理水平稳健增长

某资产管理机构是国内首批成立的资产管理公司之一&#xff0c;坚持“科技金融”、“数字金融”战略&#xff0c;以客户为中心&#xff0c;聚焦用户体验与业务协同&#xff0c;着力推进营销数字化进程和大数据平台建设&#xff0c;助力资产管理高质量发展。 数字科技推动工作效率…

PHP 正则式 全能匹配URL(UBB)

PHP 正则式 全能匹配URL&#xff08;UBB&#xff09; 语言&#xff1a;PHP 注明&#xff1a;正则式 无语言限制&#xff08;js、PHP、JSP、ASP、VB、.net、C#...&#xff09;一切皆可。 简介&#xff1a;PHP UBB 正则式 全能匹配URL 自动加超级链接。网上找了很多都不匹配或…

让国内AI模型解题:滑动窗口中找出最大值,文心一言,通义千问错误率100%,讯飞星火略胜一筹

最近&#xff0c;一些大厂陆续放出了自己的AI模型&#xff0c;处于日常的使用和准确度&#xff0c;我通过一道试题来看一下文心一言、讯飞星火和通义千万的回答结果 本道题是一道很经典的算法题&#xff0c;请在滑动窗口中找出最大值 文心一言 第一次给出答案 package main…

微服务实战系列之签名Sign

前言 昨日恰逢“小雪”节气&#xff0c;今日寒风如约而至。清晨的马路上&#xff0c;除了洋洋洒洒的落叶&#xff0c;就是熙熙攘攘的上班族。眼看着&#xff0c;暖冬愈明显了&#xff0c;叶子来不及泛黄就告别了树。变化总是在不经意中发生&#xff0c;容不得半刻糊涂。 上集博…

卫星信号监测可靠性提升10倍,移动性提升30倍!这家广播公司是这样做到的

来源&#xff1a;德思特测试测量 德思特案例 | 卫星信号监测可靠性提升10倍&#xff0c;移动性提升30倍&#xff01;这家广播公司是这样做到的 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 1 广播卫星频谱监测背景 频率的测量结果能够充分反映出节目播出的质量&am…

酷开科技 | 大胆解锁酷开系统,收获绝佳观影体验

在当今数字化时代&#xff0c;家庭娱乐的方式越来越多样化。其中&#xff0c;看电影无疑是很受欢迎的一种。而酷开科技&#xff0c;作为一家专注于智能电视领域的科技公司&#xff0c;其自主研发的智能电视操作系统——酷开系统&#xff0c;就在看电影方面为消费者带来了更精彩…