当前位置: 首页 > ai >正文

【C++】特殊类设计

🦄个人主页:修修修也

🎏所属专栏:C++

⚙️操作环境:Visual Studio 2022


目录

📌不能被拷贝的类

设计思路

具体实现

📌只能在堆上创建对象的类

设计思路

具体实现

📌只能在栈上创建对象的类

设计思路

具体实现

📌不能被继承的类

设计思路

具体实现

📌只能创建一个对象的类(单例模式)

什么是设计模式

什么是单例模式

饿汉模式

懒汉模式

结语


 

        在日常的开发中, 我们可能会遇到以下几种类的设计需求, 比如我们要求这个类不能被拷贝, 又或者这个类只能在堆/栈上创建对象等等。下面为大家介绍一下这些类的设计思路:

📌不能被拷贝的类

设计思路

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

具体实现

C++98版:

        首先我们要知道, 拷贝构造和赋值运算符重载如果我们自己不手动实现, 那么系统是会默认给自动生成的, 为了防止系统自动生成的导致可以使用构造函数和赋值运算符重载, 所以我们要自己手动实现后再禁用。

        具体做法就是: 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
        原因:
        1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
能禁止拷贝了
        2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

        具体实现代码:

class CopyBan
{
public:CopyBan(int val):a(val){}//...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);int a;//...
};

        测试结果:可以看到, 禁用后该类确实无法被拷贝了

C++11版:

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

        具体实现代码:

class CopyBan
{
public:CopyBan(int val):a(val){}CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;int a;
};

        测试结果: 可以看到删除后该类同样无法进行拷贝了


📌只能在堆上创建对象的类

设计思路

        要让对象只能在堆上创建, 核心是要阻止栈/全局/静态对象的构造, 因此我们的思路1是:

1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

        思路2是:

        因为栈区/全局/静态的内存空间都是由操作系统自动管理的, 所以这三个地方创建的对象出了生命周期会自动调用析构函数, 那我们直接把析构函数私有化, 这样系统检测不到析构函数, 自然就不会允许在栈区/全局/静态区创建对象了。

具体实现

        思路1实现代码:

//只能在堆上创建
class HeapOnly
{
public:static HeapOnly* CreateObject(int val){return new HeapOnly(val);}
private:HeapOnly(int val):a(val){}HeapOnly(const HeapOnly&) = delete;int a;
};

        测试结果: 可以看到, 除了调用封装的固定Create接口在堆上创建对象, 其他任何形式想创建栈上的或全局的或静态的都不可以。

        思路2实现代码:

class HeapOnly
{
public:HeapOnly(int val):a(val){}void Destory(){delete this;}
private:~HeapOnly(){}int a;
};

        测试结果:


📌只能在栈上创建对象的类

设计思路

        要让对象只能在栈上创建, 核心是要禁用堆内存分配操作,同时允许栈内存分配, 因此我们的思路是:

1. 将类的构造函数私有,禁用operator new和operator delete函数。防止别人调用在堆上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建并返回给外部。

具体实现

        实现代码:

//只能在栈上创建
class StackOnly
{
public:static StackOnly CreateObject(int val){StackOnly st(val);return st;}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;
private:StackOnly(int val):a(val){}int a;
};

        测试结果:


📌不能被继承的类

设计思路

        C++98实现思路: 

                将基类构造函数私有化, 因为规定派生类初始化时必须调用基类的构造函数, 那将基类构造函数私有化之后, 派生类无法访问基类构造函数, 于是也无法构造对象, 故无法继承。

        C++11实现思路:

                使用final关键字, final关键字修饰的类无法被继承。

具体实现

        c++98实现:

//不能被继承的类
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};

        测试结果: 果然派生类无法正常使用构造函数创建对象:

        C++11实现:

class NonInherit final
{
public:NonInherit(){}
};

        测试结果:


📌只能创建一个对象的类(单例模式)

什么是设计模式

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

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

什么是单例模式

        一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。 

        单例模式的设计思想首先就是将构造函数私有化, 外部不能够随意创建实例, 只能通过给定的接口去拿到那个唯一的实例, 而这个唯一的实例是通过静态成员来实现的。

        就比如我们日常开发中经常使用到的日志类,  就是一个很适合使用单例模式来实现的类。即全局只需要一个log对象, 后续有任何日志信息需要输出, 就直接调用那一个唯一的全局log对象即可。针对于唯一实例创建的时机不同,  单例模式有两种实现模式, 饿汉模式和懒汉模式, 下面分别为大家介绍一下:

饿汉模式

        饿汉模式, 顾名思义就是我有食物我就去吃。对应到单例模式的设计里, 就是我不管后续会不会用到这个单例, 程序启动时就去把这个唯一的实例对象创建出来, 这是饿汉模式对于单例创建的一个设计思想。

        具体到实际代码的实现上, 我们就选择将静态成员声明在类里, 直接在全局定义,这样程序一启动这个实例就会被初始化。

        注意, 饿汉模式思想的优点是: 实现比较简单, 不需要考虑多线程中单例模式的安全问题。 

        但饿汉模式思想的缺点是: 如果这个实例创建后没有被使用, 并且这个实例自身占用了非常多的资源的话, 那么将会大大拖慢程序的启动速度, 从而导致程序性能下降。并且如果有两个单例类A和B, B是依赖A实例化的, 那么饿汉模式是无法保证它们的实例化顺序的, 这样就可能会导致实例化失败, 同样也是饿汉模式的一大无法避免的问题。

        实现代码:

//饿汉模式
class EagerSingleton 
{
public:static EagerSingleton& getInstance() {return _instance; // 直接返回已初始化的静态实例}void doSomething() { /* 操作单例对象的业务方法 */ }// 禁用拷贝和赋值保证单例特性EagerSingleton(const EagerSingleton&) = delete;EagerSingleton& operator=(const EagerSingleton&) = delete;private:EagerSingleton() = default; // 私有构造函数~EagerSingleton() = default;static EagerSingleton _instance; // 静态成员声明
};// 静态成员类外定义
EagerSingleton EagerSingleton::_instance;int main()
{EagerSingleton::getInstance().doSomething();return 0;
}

懒汉模式

        懒汉模式, 顾名思义就是我饿了我再去吃。对应到单例模式的设计里, 就是我后续用到这个单例了, 我再去创建这个唯一的实例对象。这就是懒汉模式对于单例创建的一个设计思想。

        具体到实际代码的实现上,我们选择将静态成员设置为一个单例对象指针, 然后在get函数里再去实现如果第一次被调用就new一个实例出来, 从而做到了使用时实例化。

        注意, 懒汉模式思想的优点是: 不会拖慢程序的启动速度, 只有有需要的时候才会去初始化, 所以程序性能会比饿汉模式要高。并且因为是根据需要初始化的, 所以可以保证不同单例初始化的前后顺序。

        但饿汉模式思想的缺点是: 实现比较难, 需要考虑多线程中创建单例模式的安全问题

        常规实现代码:

//懒汉模式
class LazySingleton
{
public:static LazySingleton& getInstance(){if (_pinstance == nullptr)//双重检查, 只有第一次创建实例时才加锁{unique_lock<mutex> lock(_mtx);if (_pinstance == nullptr)//如果是第一次调用,则创建实例{_pinstance = new LazySingleton;}}return *_pinstance; // 返回已初始化的静态实例}//防止需要显示调用释放单例static void DelInstance(){if (_pinstance){delete _pinstance;_pinstance = nullptr;}}void doSomething(){ /* 操作单例对象的业务方法 */}// 禁用拷贝和赋值保证单例特性LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;private:LazySingleton() = default; // 私有构造函数~LazySingleton() = default;static LazySingleton* _pinstance; // 静态成员声明static mutex _mtx;
};// 静态成员类外定义
LazySingleton* LazySingleton::_pinstance;

        C++11之后的编译器保证局部静态变量的初始化是线程安全的, 极简实现代码:

//懒汉模式
class LazySingleton
{
public:static LazySingleton& getInstance(){//局部的静态对象, 是在第一次调用时初始化//C++11之前这里不是线程安全, C++11之后可以保证局部静态对象的初始化是线程安全的static LazySingleton inst;return inst; }void doSomething(){ /* 操作单例对象的业务方法 */}// 禁用拷贝和赋值保证单例特性LazySingleton(const LazySingleton&) = delete;LazySingleton& operator=(const LazySingleton&) = delete;private:LazySingleton() = default; // 私有构造函数~LazySingleton() = default;
};

结语

希望这篇关于 特殊类设计 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【C++11】智能指针

【C++11】左值引用、右值引用、移动语义和完美转发

【C++】STL标准模板库容器set

【C++】模拟实现二叉搜索(排序)树

【C++】模拟实现priority_queue(优先级队列)

【C++】模拟实现queue

【C++】模拟实现stack

【C++】模拟实现list

【C++】模拟实现vector

【C++】标准库类型vector

【C++】模拟实现string类

【C++】标准库类型string

【C++】构建第一个C++类:Date类

【C++】类的六大默认成员函数及其特性(万字详解)

【C++】什么是类与对象?


http://www.xdnf.cn/news/5298.html

相关文章:

  • 支持向量机与逻辑回归的区别及 SVM 在图像分类中的应用
  • matlab中的积分函数
  • 【Java学习日记34】:this关键字和成员变量
  • armv7 backtrace
  • LoRA(Low-Rank Adaptation)原理详解
  • 【ajax基础】
  • 深入理解深度Q网络DQN:基于python从零实现
  • OB Cloud 云数据库V4.3:SQL +AI全新体验
  • redis主从同步于对象模型
  • 【基于 LangChain 的异步天气查询2】GeoNames实现地区实时气温查询
  • EDITPLUS配置CTags实现函数跳转
  • 技术方案模型需要兼顾战略规划、技术实现与落地可行性
  • 《操作系统真象还原》第十三章——编写硬盘驱动程序
  • SQL注入问题
  • powerbuilder9.0中文版
  • 7、系统开发
  • 计算机网络 4-2-1 网络层(IPv4)
  • 每日算法-250510
  • 深入理解Embedding技术-什么是Embedding?
  • 使用fdisk 、gdisk管理分区
  • Satori:元动作 + 内建搜索机制,让大模型实现超级推理能力
  • python:ASCII-generator 实用教程
  • $\int_{0}^{1} x \arcsin \sqrt{4x - 4x^2}dx$
  • LintCode第366题-斐波那契数列
  • 各种环境测试
  • 解释器和基于规则的系统比较
  • 【Linux基础】文件和目录管理指令
  • 对日开发 TeraTerm ttl脚本开发环境配置
  • python04——条件判断(选择结构)
  • 部署RocketMQ