首页 > 编程语言 >C++特殊类的设计与单例模式

C++特殊类的设计与单例模式

时间:2024-02-27 21:23:52浏览次数:22  
标签:std Singleton getInstance StackOnly int 模式 static C++ 单例

// 1. 设计一个不能被拷贝的类 /* 解析:拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C++98; 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。 原因: 1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了 2. 只声明不定义:防止编译器自动生成,且没必要定义,原本就是不想使用 C++11: C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。 class CopyBan //ban 禁止 , 禁止拷贝 {    // ...    CopyBan(const CopyBan&)=delete;    CopyBan& operator=(const CopyBan&)=delete;    //... }; h*/ //2.设计一个只能在堆上创建对象的类 //------------------------------------------------------------ 只能在堆上创建对象的类 /* //思路1:隐藏析构函数 class HeapOnly { public: void Destory() { delete this; } private: ~HeapOnly() { std::cout << "~HeapOnly()" << std::endl; } //public: int _x; }; int main() { //HeapOnly ho1; //报错,没有可访问的析构函数类不能在栈上定义对象,因为栈上的对象是由编译器负责析构的,但类外不能访问私有,即编译器调不到析构,释放不了,所以会有问题 //HeapOnly ho2(); //未知,编译器自动生成的行为不可控制 //static HeapOnly ho4; //静态区的对象也受编译器控制析构,所以也不能正常定义 HeapOnly *ho3 = new HeapOnly; //没有可访问的析构函数类只能在堆上定义对象,因为堆上的资源由用户自己释放,所以可以正常定义 //delete ho3; //必须是公开的析构函数,才能在栈上使用delete(在栈域访问类域私有成员) ho3->Destory(); //进到类域内部的函数能够访问私有成员,可以析构 return 0; } */ //思路2:隐藏构造函数(常用,通用) //封构造函数能够把在栈上 定义或new 封掉 /* class HeapOnly { public: static HeapOnly* createObj(int x = 0) // static函数在类域内,不受访问修饰符约束,能够看得见成员函数 { HeapOnly* p = new HeapOnly(x); //在类域内new一个对象,就是在类域内调用构造,定义的空间位于堆区 return p; } private: HeapOnly(int x = 0) :_x(0) {} HeapOnly(const HeapOnly& ho) = delete; HeapOnly& operator=(const HeapOnly& ho) = delete; int _x; }; int main() { HeapOnly* ho = HeapOnly::createObj(0); //在栈上定义一个指针,调用类域内的静态函数createObj去new一个对象,得到对象的指针 //HeapOnly ho1(*ho); //还需要禁掉拷贝构造 return 0; } */ //------------------------------------------------------------ 只能在堆上创建对象的类__End; // 设计只能在栈上定义类(不能完全做到,最多只能禁止堆,不能禁止static区) /* class StackOnly { public: static StackOnly createObj(int x = 0) { return StackOnly(x); //传值返回 } static StackOnly create_move_Obj(int x = 0) { return std::move(StackOnly(x)); //画蛇添足 } StackOnly(const StackOnly& ho):_x(ho._x) {} StackOnly(StackOnly&& ho):_x(ho._x) {} private: StackOnly(int x = 0):_x(0) {} int _x; }; int main() { StackOnly ho = StackOnly::createObj(0); //传值返回 -- 需要拷贝构造 static StackOnly sho = ho; // 有拷贝构造就能创建static对象 StackOnly ho1 = StackOnly::create_move_Obj(1); //如果禁止掉拷贝构造而使用移动构造 static StackOnly sho1 = std::move(ho1); // 有移动构造也能创建static对象 return 0; } */ //设计一个不能被继承的类 /* C++98:构造函数私有化 // 原因: 派生类在初始化时必须要显式调用父类构造函数帮助父类私有化, 父类构造函数私有后,子类就无法完成初始化了 C++11:使用final关键字,final修饰类,表示该类不能被继承 class A  final //最终 {    // .... }; */ //设计模式 /* 设计模式: 设计模式(Design Pattern)是一套被反复使用、多数人知晓、经过分类的、代码设计经验的总结。 为什么会产生设计模式这样的东西呢? 就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的, 后来孙子就总结出了《孙子兵法》. -- 类似 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化; 设计模式是软件工程的基石脉络,如同大厦的结构一样. //C++不太关注设计模式,Java比较关注 C++常见设计模式: 适配器 迭代器 单例 (最广泛) 工厂 观察者 */ //单例模式 singleton pattern /* 在一个进程中,一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点(getInstance),该实例被所有程序模块共享。 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取, 然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理 单例模式有两种实现模式: 1.饿汉模式 2. 懒汉模式 使用场景:只要某个类的对象在全局只有一份,就可以使用单例模式 单例模式的实现: 1.把这些数据放进一个类里面,把这个类设计成单例类 2.把这个类设计成只允许在堆上实例的类 3.选择饿汉或懒汉模式 */ //饿汉模式: /* 进程一启动就会创建一个唯一的实例对象,在main函数执行前就创建了. // 优点:简单,没有线程安全问题(因为main函数之前没有多线程,而饿汉在main之前就new了) // 缺点:可能会导致进程启动慢(如果单例对象很大),且会占用资源(没用也创建), 且如果有多个单例类对象实例启动顺序不确定(main内的代码执行流是确定的,其他的不确定。 如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。 --- 怎么体现 class Singleton { public: static Singleton* getInstance() { return _ins; } void Add(const std::string& s) { _mtx.lock(); _v.push_back(s); _mtx.unlock(); } void Print() { _mtx.lock(); for (auto& v : _v) { std::cout << v << std::endl; } _mtx.unlock(); } private: Singleton() {} Singleton(const Singleton& ins) = delete; //如果没有加锁,则有防拷贝作用 //禁止拷贝构造后,operator可以不防止(调不到拷贝构造了),不防止有没有其他问题还不清楚,最好还是写 std::vector _v; // 不用给大小,对象实例化后会自动定义一个空的,后面push_back即可 static Singleton* _ins; std::mutex _mtx; }; Singleton* Singleton::_ins = new Singleton; //在堆上实例化 int main() { //Singleton::getInstance()->Add("张三"); //Singleton::getInstance()->Add("李四"); //Singleton::getInstance()->Add("王五"); //Singleton::getInstance()->Print(); int n = 10; std::thread t1([n]() { for (int i = 0; i < n; i++) { srand((size_t)time(0)); //srand只在当前进程有效 Singleton::getInstance()->Add(std::string("线程1: ") + std::to_string(rand())); } }); std::thread t2([n]() { for (int i = 0; i < n; i++) { srand((size_t)time(0)); Singleton::getInstance()->Add(std::string("线程2: ") + std::to_string(rand())); } }); t1.join(); t2.join(); Singleton::getInstance()->Print(); return 0; } */ //懒汉模式: /* 如果单例对象一开始就进行初识化,构造十分耗时或者占用很多资源,还伴随一些IO行为如 加载插件,初始化网络连接,读取文件等等, 而且有可能该对象在程序运行时不会用到,就会导致程序启动时非常缓慢。这种情况使用懒汉模式(延迟加载)更好. 懒汉模式在启动后能够使用多线程,一个线程IO,另一个线程执行业务等.一个线程不会影响另一个线程,能够缓解慢、卡顿等问题(多核的好处) 多核,并发能让不关联的业务分别独立执行,提高效率 优点:只在初次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序能够自由控制. 能够解决饿汉的所有缺点 缺点:复杂 class Singleton { public: static Singleton* getInstance() { //双检查加锁 //设计模式专业术语:双重检查锁定模式一种软件设计模式,用来减少并发系统中竞争和同步的开销。 //双重检查锁定模式首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。 if (!_ins) //提高效率,减少加锁解锁 { _imtx.lock(); if (!_ins) //用于保证线程安全和 只new一次 { _ins = new Singleton; } _imtx.unlock(); } return _ins; } //手动释放_ins对象,手动调用del并不会影响到_gc,因为静态类型不受对象控制 static void delInstance() //为什么是静态? 因为没有对象,只有类型,通过类域展开来调用 { //不需要双检查加锁了,因为销毁对象极少调用.开销不大 _imtx.lock(); if (_ins) { delete _ins; //之后会自动调用_ins的析构 _ins = nullptr; } _imtx.unlock(); } //资源自动回收对象GC struct GC { ~GC() { std::cout<<"~GC"<<std::endl; delinstance();="" }="" };="" static="" struct="" gc="" _gc;="" 声明一个静态gc成员,静态成员不受_ins对象控制,只在生命周期到了(结束结束)后自动释放="" 因为定义在堆的对象在程序结束时不会自动释放="" delete,所以设计gc辅助="" 1.当程序快结束前(main="" return之后),静态变量的生命周期结束,os会自动delete静态变量,即~gc且释放_gc,然后delinstance,~singleton且释放_ins="" 因此gc能够保证单例对象能在程序结束时自动完成析构和释放工作="" void="" add(const="" std::string&="" s)="" {="" _vmtx.lock();="" _v.push_back(s);="" _vmtx.unlock();="" print()="" for="" (auto&="" v="" :="" _v)="" std::cout="" <<="" std::endl;="" ~singleton()="" 对象要没的时候(delete="" this)才会调用="" 持久化操作...="" (持久化就是变成长久保存,一般就是写到文件,文件在磁盘中,就是持久化的一种实现)="" 要求在析构时,将数据保存在文件中.="" private:="" singleton()="" {}="" singleton(const="" singleton&="" ins)="delete;" 如果没有加锁,则有防拷贝作用="" 禁止拷贝构造后,operator可以不防止(调不到拷贝构造了),不防止有没有其他问题还不清楚,最好还是写="" std::vector<std::string=""> _v; // 不用给大小,对象实例化后会自动定义一个空的,后面push_back即可 std::mutex _vmtx; static Singleton* _ins; static std::mutex _imtx; //// 静态锁不属于对象,在静态区,锁的是静态区的对象 }; Singleton* Singleton::_ins = new Singleton; //在堆上实例化 std::mutex Singleton::_imtx; Singleton::GC Singleton::_gc; int main() { //Singleton::getInstance()->Add("张三"); //Singleton::getInstance()->Add("李四"); //Singleton::getInstance()->Add("王五"); //Singleton::getInstance()->Print(); int n = 10; //srand((size_t)time(0)); 影响不到两个线程,定不定义无所谓 std::thread t1([n]() { for (int i = 0; i < n; i++) { //srand((size_t)time(0)); //srand只在当前进程有效,但这里定义变化不大了,需要寻求新的随机数方法 Singleton::getInstance()->Add(std::string("线程1: ") + std::to_string(rand())); } }); std::thread t2([n]() { for (int i = 0; i < n; i++) { //srand((size_t)time(0)); Singleton::getInstance()->Add(std::string("线程2: ") + std::to_string(rand())); } }); t1.join(); t2.join(); Singleton::getInstance()->Print(); //Singleton::delInstance(); //手动释放, return 0; //程序结束才会释放静态,因为静态不受对象控制,只随生命周期(整个程序运行期间) } //懒汉模式方式2 class Singleton { public: static Singleton* GetInstance() { // C++11之前,这里不能保证初始化静态对象的线程安全问题 // C++11之后,这里可以保证初始化静态对象的线程安全问题 --- 很复杂,有机会再研究 static Singleton inst; //局部静态对象只会在第一调用时初始化 return &inst; } void Add(const string& str) { _vmtx.lock(); _v.push_back(str); _vmtx.unlock(); } void Print() { _vmtx.lock(); for (auto& e : _v) { cout << e << endl; } cout << endl; _vmtx.unlock(); } ~Singleton() { // 持久化 // 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好 } private: // 限制类外面随意创建对象 Singleton() { cout << "Singleton()" << endl; } // 防拷贝 Singleton(const Singleton& s) = delete; Singleton& operator=(const Singleton& s) = delete; private: mutex _vmtx; vector _v; }; int main() { Singleton::GetInstance(); Singleton::GetInstance(); return 0; } */ //还可以给_instance加volatile关键字 防止过度优化,但后续可能有很多细节要处理.可加可不加

标签:std,Singleton,getInstance,StackOnly,int,模式,static,C++,单例
From: https://www.cnblogs.com/DSCL-ing/p/18038382

相关文章

  • C++11的类型转换
    //C类型转换/*C语言:显式和隐式类型转换1.隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败2.显式类型转化:需要用户自己处理.隐式类型:界定:相关类型,相近类型,意义相似的类型可以发生类型转换,如整型和浮点可以发生类型转换,因为他们都表示数据的大......
  • 【现代C++】2.强化语言运行期的强化
    1.Lambda表达式lambda表达式实际提供了一个类似匿名函数的特性,匿名函数是在需要一个函数,但是又不想费力去命名一个函数的情况下使用的。1.1Lambda表达式基本语法[捕获列表](参数列表)mutable(可选)异常属性->返回类型{//函数体}捕获列表分为以下几种:1.1.1值捕......
  • C++11新特性的一些用法举例①
    //字符串字面量/*常用:1.原始字符串字面量---括号内保持原样输出---没有转义字符,如\n不再是换行,而是直接输出字面量\nR"(str)";实例:R"(aa\a"b"bb)";//print:aa\a"b"bb注意:constchar*s1=R"foo(HelloWorld)foo";打印结果:HelloWorld;----//&qu......
  • 详解在 centos 中引导到救援模式
    详解在centos中引导到救援模式Linux系统CentOS进入单用户模式和救援模式详解一、概述目前在运维日常工作中,经常会遇到服务器异常断电、忘记root密码、系统引导文件损坏无法进入系统等等操作系统层面的问题,给运维带来诸多不便,现将上述现象的解决方法和大家分享一下,本次主要以C......
  • Ubuntu如何进救援模式
    linux的救援模式-1详解在Ubuntu中引导到救援模式或紧急模式这篇教程将介绍如何在Ubuntu22.04、20.04和18.04LTS版本中引导到救援Rescue模式或紧急Emergency模式。你可能已经知道,在RHEL7、RHEL8、Ubuntu16.04LTS及其更新的版本的Linux发行版中运行等级R......
  • C++ STL 容器 list类型
    C++STL容器list类型list对于异常支持很好,要么成功,要么不会发生什么事情以下是std::list在异常处理方面表现良好的几个原因:动态内存管理:std::list使用动态内存分配来存储元素,这意味着它会在需要时自动分配内存,并在不再需要时释放内存。这种自动管理可以减少内存泄漏和悬......
  • C++ STL 容器-Deque
    C++STL容器-Dequestd::deque(双端队列)是C++标准模板库(STL)中的一个容器,它支持在序列的两端快速插入和删除元素。与std::vector和std::list等其他序列容器相比,std::deque在某些特定场景下具有独特的优势。元素的访问和迭代比vector慢,迭代器不是普通的指针。以下是std::deque的一......
  • 《黑暗欺骗》c++控制台 2D 版!Alpha 0.2.1
    现在只打了设置这些个东西,游戏主体还没打,那才是难点已实现功能游戏未实现设置有音量设置和键盘设置两个功能存档(这好像是最简单的功能吧?)注意事项!!!没错,如你所见,这个游戏我是使用了MCI来播放声音的!因此,你的DEV-C++需要链接到一个库打开工具->编译选项->编译器-......
  • [1] C++编程语言
    week9day1 输出指令//控制台打印std::cout<<"HelloWorld";//简化std命名空间usingnamespacestd;//转义字符cout<<"\n";//\n会被渲染成前面有\的前提下\n不会被渲染cout<<"\\n";\n;<<endl;换行; 系统指令//system()可以调用CMD......
  • c++ bind this 实现成员函数代替静态函数
    bind可以用成员函数来替代静态函数。回调函数一般使用静态函数,其中需要传入具体对象的指针,然后该对象的成员变量或函数,都需要加上“对象指针->”这个前缀。bind可以将成员函数用于回调函数。成员函数多了一个隐含的参数this,所以直接用作回调会报错,bind可以将this封装起来(可以理......