iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >一篇文章带你了解C++特殊类的设计
  • 282
分享到

一篇文章带你了解C++特殊类的设计

2024-04-02 19:04:59 282人浏览 安东尼
摘要

目录设计一个类,只能在堆上创建对象设计一个类,只能在栈上创建对象设计一个类,不能被拷贝设计一个类,不能继承设计一个类,只能创建一个对象(单例模式)单例模式的概念单例模式的实现饿汉模式

设计一个类,只能在堆上创建对象

想要的效果实际是没法直接在栈上创建对象。

首先cpp只要创建对象就要调用构造函数,因此先要把构造函数ban掉,把构造函数设计成private。但是单这样自己也创建不了了。

因此提供一个创建的接口,只能调用该接口,该接口内部写new。而且要调用该接口需要先有对象指针调用,而要有对象先得调用构造函数实例化,因此必须设计成静态函数

但是注意这样还有拷贝函数可以调用HeapOnly copy(*p)。此时生成的也是栈上的对象。因此要拷贝构造私有,并且只声明不实现(实现也是可以的,但是没人用)。这种方式在c++98中叫防拷贝,比如互斥

#include<iOStream>
using namespace std;
class HeapOnly
{
private:
	HeapOnly()
	{ }
    //C++98——防拷贝
    HeapOnly(const HeapOnly&);
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
};
int main()
{
	HeapOnly* p = HeapOnly::CreateObj();
	return 0;
}

对于防拷贝,C++11中有新的方式。函数=delete

#include<iostream>
using namespace std;
class HeapOnly
{
private:
	HeapOnly()
	{ }
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
    //C++11——防拷贝
    HeapOnly(const HeapOnly&) =delete;
};
int main()
{
	HeapOnly* p = HeapOnly::CreateObj();
	return 0;
}

总结:

1.将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。

2.提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

设计一个类,只能在栈上创建对象

  • 方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可。

由于返回临时对象,因此不能禁掉拷贝构造。

class StackOnly 
{ 
    public: 
    static StackOnly CreateObject() 
    { 
        return StackOnly(); 
    }
    private:
    StackOnly() {}
};
  • 方法二:调用类自己的专属的operator new和operator delete,设置为私有。

因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可。注意:也要防止定位new。new先调用operator new申请空间,然后调用构造函数。delete先调用析构函数释放对象所申请的空间,再调用operator delete释放申请的对象空间。

class StackOnly 
{ 
    public: 
    StackOnly() {}
    private: //C++98
    void* operator new(size_t size);
    void operator delete(void* p);
};
int main()
{
  	static StackOnly st;//缺陷,没有禁掉静态区的。  
}
class StackOnly 
{ 
    public: 
    StackOnly() {}
    //C++11
    void* operator new(size_t size) = delete;
    void operator delete(void* p) = delete;
};
int main()
{
  	static StackOnly st;//缺陷,没有禁掉静态区的。  
}

设计一个类,不能被拷贝

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

  • C++98将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
    // ...
    private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因:

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

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

  • C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
    // ...
    CopyBan(const CopyBan&)=delete;
    CopyBan& operator=(const CopyBan&)=delete;
    //...
};

设计一个类,不能继承

C++98

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
    public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
    private:
    NonInherit()
    {}
};
class B : public NonInherit
{};
int main()
{
    //C++98中这个不能被继承的方式不够彻底,实际是可以继承,限制的是子类继承后不能实例化对象
    B b;
    return 0;
}

C++11为了更直观,加入了final关键字

class A final
{   };
class C: A
{};

设计一个类,只能创建一个对象(单例模式)

之前接触过了适配器模式和迭代器模式。

可以再看看工厂模式,观察者模式等等常用一两个的。

单例模式的概念

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

为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

  • 单例模式

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

1.如何保证全局(一个进程中)只有一个唯一的实例对象

参考只能在堆上创建对象和在栈上创建对象,禁止构造和拷贝构造及赋值。

提供一个GetInstance获取单例对象。

2.如何提供只有一个实例呢?

饿汉模式和懒汉模式。

单例模式的实现

饿汉模式

饿汉模式:程序开始main执行之前就创建单例对象,提供一个静态指向单例对象的成员指针,初始时new一个对象给它。

class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            return _inst;
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {}
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	static Singleton* _inst;
    	int _val;
};
Singleton* Singleton::_inst = new Singleton;
int main()
{
    cout<<Singleton::GetInstance()<<endl;
    cout<<Singleton::GetInstance()<<endl;
    cout<<Singleton::GetInstance()<<endl;
    Singleton::GetInstance()->Print();
}

懒汉模式

懒汉模式

懒汉模式出现的原因,单例类的构造函数中要做很多配置初始化工作,那么饿汉就不合适了,会导致程序启动很慢。

linux是Posix的pthread库,windows下有自己的线程库。因此要使用条件编译保证兼容性。因此c++11为了规范提供了语言级别的封装(本质也是条件编译,库里实现了)。

关于保护第一次需要加锁,后面都不需要加锁的场景的可以使用双检查加锁。

#include<mutex>
#ifdef _WIN32
//windos 提供多线程api
#else
//linux pthread
#endif //
class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            //保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
            //特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率
            if( _inst == nullptr)
            {
                _mtx.lock();
                if( _inst == nullptr ) 
                {
                    _inst = new Singleton;
                }
                _ntx.unlock();
            }
            return _inst;
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {}
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	static Singleton* _inst;
    	static std::mutex _mtx;
    	int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默认无参构造函数
int main()
{
    Singleton::GetInstance()->Print();
}

饿汉模式和懒汉模式的对比

  • 饿汉模式
    • 优点:简单
    • 缺点:
      • 如果单例对象构造函数工作比较多,会导致程序启动慢,迟迟进不了入口main函数。
      • 如果有多个单例对象,他们之间有初始化的依赖关系,饿汉模式也会有问题。比如有A和B两个单例类,要求A单例先初始化,B必须在A之后初始化,那么饿汉无法保证。这种场景下用懒汉模式,懒汉可以先调用A::GetInstance(),再调用B::GetInstance()。
  • 懒汉模式
    • 优点:解决了饿汉的缺点,因为他是第一次调用GetInstance时创建初始化单例对象
    • 缺点:相对饿汉复杂一点。

懒汉模式的优化

实现了”更懒“。

缺点:单例对象在静态区,如果单例对象太大,不合适。再挑挑刺,这个静态对象无法主动控制释放。

#include<mutex>
#ifdef _WIN32
//windos 提供多线程api
#else
//linux pthread
#endif //
//其他版本懒汉
class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            static Singleton inst;
            return &inst;
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {}
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	static std::mutex _mtx;
    	int _val;
};
std::mutex Singleton::_mtx;//()默认无参构造函数
int main()
{
    Singleton::GetInstance()->Print();
}
#include<mutex>
#ifdef _WIN32
//windos 提供多线程api
#else
//linux pthread
#endif //
//其他版本懒汉
class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            static Singleton inst;
            return &inst;
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {}
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	static std::mutex _mtx;
    	int _val;
};
std::mutex Singleton::_mtx;//()默认无参构造函数
int main()
{
    Singleton::GetInstance()->Print();
}

单例对象的释放

单例对象一般不需要释放。全局一直用的不delete也没问题,进程如果正常销毁,进程会释放对应资源。

单例对象的直接释放

#include<mutex>
#ifdef _WIN32
//windos 提供多线程api
#else
//linux pthread
#endif //
class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            //保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
            //特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率
            if( _inst == nullptr)
            {
                _mtx.lock();
                if( _inst == nullptr ) 
                {
                    _inst = new Singleton;
                }
                _ntx.unlock();
            }
            return _inst;
        }
    	static void DelInstance()
        {
            _mtx.lock();
            if(!_inst)
            {
                delete _inst;
                _inst=nullptr;
            }
            _mtx.unlock();
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {
          	//假设单例类构造函数中,需要做很多配置初始化   
        }
    	~Singletion()
        {
            //程序结束时,需要处理一下,持久化保存一些数据
        }
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	static Singleton* _inst;
    	static std::mutex _mtx;
    	int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默认无参构造函数
int main()
{
    Singleton::GetInstance()->Print();
}

内部垃圾回收类

上述场景其实还是可以扩展的。

假设析构函数有一些数据需要保存一下,持久化一下,不调用析构函数会存在问题,因此需要调用析构函数的时候处理。这就得保证main函数结束的时候保证调用析构(private)。

但是显式调用DelInstance可能会存在遗忘。

#include<mutex>
#ifdef _WIN32
//windos 提供多线程api
#else
//linux pthread
#endif //
class Singleton
{
    public:
    	static Singleton* GetInstance()
        {
            //保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
            //特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率
            if( _inst == nullptr)
            {
                _mtx.lock();
                if( _inst == nullptr ) 
                {
                    _inst = new Singleton;
                }
                _ntx.unlock();
            }
            return _inst;
        }
    	void Print()
        {
            cout<<"Print() "<<_val<<endl;
        }
    private:
    	Singleton()
        :_val(0)
        {
          	//假设单例类构造函数中,需要做很多配置初始化   
        }
    	~Singletion()
        {
            //程序结束时,需要处理一下,持久化保存一些数据
        }
    	Singleton(const Singleton& ) =delete;
    	Singleton(const Singleton& ) =delete;
    	//实现一个内嵌垃圾回收类
    	class CGarbo{
            public:
            	~CGarbo()
                {
                    //DelInstance();
                	if(_inst)
                    {
                         delete _inst;
                        _inst = nullptr;
                    }
                }
        }
    	static Singleton* _inst;
    	static std::mutex _mtx;
    	static GCarbo _gc;//定义静态gc对象,帮助我们进行回收
    	int _val;
};
Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;//()默认无参构造函数
Singleton::CGarbo Singleton::_gc;
int main()
{
    Singleton::GetInstance()->Print();
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!   

--结束END--

本文标题: 一篇文章带你了解C++特殊类的设计

本文链接: https://www.lsjlt.com/news/140543.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 一篇文章带你了解C++特殊类的设计
    目录设计一个类,只能在堆上创建对象设计一个类,只能在栈上创建对象设计一个类,不能被拷贝设计一个类,不能继承设计一个类,只能创建一个对象(单例模式)单例模式的概念单例模式的实现饿汉模式...
    99+
    2024-04-02
  • 一篇文章带你了解Python中的类
    目录1、类的定义2、创建对象3、继承总结1、类的定义 创建一个rectangle.py文件,并在该文件中定义一个Rectangle类。在该类中,__init__表示构造方法。其中,s...
    99+
    2024-04-02
  • 一篇文章带你了解C++中的异常
    目录异常抛出异常基本操作自定义的异常类栈解旋异常接口声明异常变量的生命周期异常的多态c++的标准异常库编写自己的异常类总结异常 在c语言中,对错误的处理总是两种方法: 1,使用整型的...
    99+
    2024-04-02
  • 一篇文章带你了解C++的KMP算法
    目录KMP算法步骤1:先计算子串中的前后缀数组NextC++代码:步骤2:查找子串在母串中出现的位置。总结KMP算法 KMP算法作用:字符串匹配 例如母串S = “aaagoogle...
    99+
    2024-04-02
  • 一篇文章带你了解JavaSE的数据类型
    目录前言Java类型汇总整型变量-int\长整型变量-long\短整形变量-short浮点型变量-double\float字符类型变量-char字节类型变-byte布尔类型变量-bo...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript的包装类型
    目录1、简介2、String1、创建语法2、常用方法3、更多方法3、Number1、语法2、属性3、常用方法4、Boolean总结1、简介 【解释】: 在 JavaScri...
    99+
    2024-04-02
  • 一篇文章带你了解C++(STL基础、Vector)
    目录STL基本概念STL六大组件STL中容器、算法、迭代器容器算法迭代器初识Vector容器Vector三大遍历算法Vector存放其他数据类型 Vector容器嵌套总结S...
    99+
    2024-04-02
  • 一篇文章带你了解C/C++的回调函数
    目录函数指针概念先来看一个Hello World程序然后,采用函数调用的形式来实现用函数指针的方式来实现函数指针数组回调函数概念标准Hello World程序将它修改成函数回调样式修...
    99+
    2024-04-02
  • 一篇文章带你深入了解Java类加载
    目录1.类加载<1>.父子类执行的顺序<2>类加载的时机<3>类的生命周期<4>类加载的过程<5>类加载器1.启动类加载器...
    99+
    2024-04-02
  • 一篇文章带你了解XGBoost算法
    目录1. 什么是XGBoost1.1 XGBoost树的定义1.2 正则项:树的复杂度1.3 树该怎么长1.4 如何停止树的循环生成2. XGBoost与GBDT有什么不同3. 为什...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-语句
    目录表达式语句复合语句和空语句复合语句空语句声明语句varfunction条件语句ifif/elseelse ifswitch循环whiledo/whileforfor/in跳转标签...
    99+
    2024-04-02
  • 一篇文章带你了解c++运算符重载
    目录友元函数重载:复合赋值Operator pairings自增自减运算符的重载c++20,spaceship operator总结友元函数 一种全局函数,可以在类里声明,其他地方定...
    99+
    2024-04-02
  • 一篇文章带你了解C++中的显示转换
    目录总结命名的强制类型转换: 形式: cast-name<type>(expression); type是强制转换的类型,expression是强制转换的值。如果...
    99+
    2024-04-02
  • 一篇文章带你了解jQuery动画
    目录1.控制元素的显示与隐藏 show() hide()2.控制元素的透明度 fadeIn() fadeOut()3:控制元素的高度 slideUp() slideDown()总结 ...
    99+
    2024-04-02
  • 一篇文章带你了解JavaScript-对象
    目录创建对象对象直接量通过new创建对象原型Object.create()属性的查询和设置继承属性访问错误删除属性检测属性序列化对象总结创建对象 对象直接量 对象直接量是由若干名/值...
    99+
    2024-04-02
  • 一篇文章带你了解Java Stream流
    目录一、Stream流引入现有一个需求:1.用常规方法解决需求2.用Stream流操作集合,获取流,过滤操作,打印输出二、Stream流的格式三、获取流四、Stream流的常用方法方...
    99+
    2024-04-02
  • 一篇文章带你了解C语言的文件操作
    目录为什么使用文件什么是文件程序文件数据文件文件名文件的打开和关闭文件指针fopen和fclose函数文件的顺序读写总结为什么使用文件 我们在想既然是通讯录就应该把信息记录下来,只有...
    99+
    2024-04-02
  • 一篇文章带你了解C语言操作符
    目录一、操作符分类 二、算术操作符三、移位操作符1、左移操作符 2、右移操作符2.1算术移位 2.2逻辑移位 四、位操作符 1、按位...
    99+
    2024-04-02
  • 一篇文章带你了解C++智能指针详解
    目录为什么要有智能指针?智能指针的使用及原理RALLshared_ptr的使用注意事项创建多个 shared_ptr 不能拥有同一个对象shared_ptr 的销毁shared_pt...
    99+
    2024-04-02
  • 一篇文章带你了解vue路由
    目录概念Vue Router简介Vue Router的特性Vue Router的使用步骤分类嵌套路由动态路由命名路由编程式导航总结概念 路由的本质就是一种对应关系,比如说我们在url...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作