C++进阶------智能指针和特殊类设计方式

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂

​🎂 作者介绍: 🎂🎂

🎂 🎉🎉🎉🎉🎉🎉🎉 🎂

🎂作者id:老秦包你会, 🎂

简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂

喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂

🎂个人主页::小小页面🎂

🎂gitee页面:秦大大🎂

🎂🎂🎂🎂🎂🎂🎂🎂

🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


智能指针

引入智能指针之前问题

当我们使用new的时候,会有可能开辟失败,如果使用异常捕获, 我们知道使用捕获之后,就会跳转到对应的catch进行处理,处理完成,执行catch后面的代码,如果到catch的这段过程,跳过了内存释放的代码,就会导致内存泄漏。

内存泄漏是啥,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

RAll

​RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

原理如下:

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做

法有两大好处:

  1. 不需要显式地释放资源。

  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

    /ARll的方式
    template<class T>
    class Smaptr
    {
    public:
    Smaptr(T* p)
    :_ptr(p)
    {

    复制代码
     }
     ~Smaptr()
     {
     	cout << "delete _ptr" << endl;
     	delete[] _ptr;
     }

    private:
    T* _ptr;
    };

    void Fun3()
    {
    Smaptr<int> sp1 = new int[10];
    Smaptr<int> sp2 = new int[20];
    Smaptr<int> sp3 = new int[30];
    }
    int main()
    {

    复制代码
     Fun3();
    
    
     return 0;

    }

实际上,上面的写法只是RALL的写法,并不是智能指针的写法。智能指针具备有像指针一样的使用比如* 、->等,就需要进行相关的运算符重载

如下:

复制代码
template<class T>
class Smaptr
{
public:
	Smaptr(T* p)
		:_ptr(p)
	{
		
	}
	~Smaptr()
	{
		cout << "delete _ptr" << endl;
		delete _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
int main()
{
	Smaptr<pair<string, int>> sp(new pair<string, int>);
	sp->first = "hjahhh";//会忽略一个->
	(sp.operator->())->second = 55;

	return 0;
}

总结一下智能指针的原理

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为。

智能指针的拷贝问题

在前面的vector或者其他的容器中,使用的拷贝往往就是深拷贝,因为拷贝过程是希望各自拥有各自的资源,互不干扰。

但是在智能指针中,里面的资源往往不是自己的,只是代为持有,方便访问修改数据, 所以拷贝的时候就是浅拷贝。这里就会引发一个问题,就是同一块地址会有重复释放的危险。

智能指针的引入

C++98版本的库中就提供了auto_ptr 的智能指针,只需导入memory头文件就可以使用

使用如下:

复制代码
int main()
{
	auto_ptr<int> sp1(new int(2));
	auto_ptr<int> sp2(sp1);
	cout << sp2.get();
	return 0;
}

调试结果:

可以看出,这个类里面有一个地址指针成员,当拷贝到第二个智能指针上的时候如下:

可以看到,sp1把资源给了sp2,也就是管理权转移,被拷贝对象把资源管理权转移给拷贝对象,被拷贝对象被悬空了,sp1不能访问了。

boost库

在C++98引入的auto_ptr后,随着时间的推移,boost库也跟着出现。

Boost 是一个由 C++ 社区开发和维护的、经过同行评审(peer-reviewed)的、可移植的 C++ 源码库。它被称为 ​​"准标准库"​​,

如果需要的话,需要去官网下载。

boost库中引入了比auto_ptr更加好用的智能指针,例如给出了更实用的scoped_ptr/scoped_array和shared_ptr/shared_array等智能指针。

boost库里面有boost命名空间,常常使用boost::进行使用

C++11智能指针

C++ 11,引入了unique_ptr和shared_ptr,和boost中的scoped_ptr以及shared_ptr一一对应。

unoque_ptr

unique_ptr的实现原理:简单粗暴的防拷贝。

复制代码
int main()
{
	unique_ptr<int> sp(new int(2));
	unique_ptr<int> sp1(sp);//不能进行拷贝
	return 0;
}

在C++98,会把拷贝构造函数只声明,不定义,声明为使用成员函数。在C++11的做法就是使用关键字delete

如下:

复制代码
namespace bit
{
	template<class T>
	class unique_ptr
	{ 
	public:
		unique_ptr(T* p)
			:_ptr(p)
		{

		}
		unique_ptr(unique_ptr<T>& sp) = delete;
		~unique_ptr()
		{
			cout << "delete _ptr" << endl;
			delete _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T* operator=(const unique_ptr<T>& sp) = delete;
			
	private:
		T* _ptr;
};

}

需要注意的是operator=为默认成员函数,是需要进行delete的,因为防止有赋值操作,

shared_ptr

这个智能指针跟boost::shared_ptr类似 ,这个智能指针使用的是引用计数 来记录和管理资源,由最后管理的人释放,

接下来我们可以尝试模拟一下,
第一想法

就是使用static来进行控制引用计数,但是这个不全面,如果有多个资源进行需要管理,这些资源共用相同的引用计数就会出问题,所以不能使用静态成员变量进行模拟。

第二想法:

使用成员变量来进行控制引用计数,拷贝就增加,但是,引用计数减少无法进行统一的减少,不能使用成员变量来。

复制代码
namespace bit1
{
	template<class T>
	class Shared_ptr
	{
	public:
		Shared_ptr(T* ptr)
		{
			_ptr = ptr;
			_count = 0;
		}
		Shared_ptr(Shared_ptr<T>& sp)
		{
			_ptr =sp._ptr;
			sp._count++;
			_count = sp._count;
		}
		~Shared_ptr()
		{
			
			if (_count == 0)
			{
				delete _ptr;
			}
			else
				_count--;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		int ues_count()
		{
			return _count;
		}
	private:
		T* _ptr;
		int _count;
		//static int _count;
	};
}
//template<class T>
//int bit1::Shared_ptr<T>::_count = 0;

正确的方式:

这个引用计数可以通过一块地址进行管理,而对应的类成员就是这块地址,一个资源匹配一个引用计数

如图:

复制代码
namespace bit1
{
	template<class T>
	class Shared_ptr
	{
	public:
		Shared_ptr(T* ptr)
		{
			_ptr = ptr;
			_count_ptr = new int(1);
		}
		Shared_ptr(Shared_ptr<T>& sp)
		{
			_ptr =sp._ptr;
			(*sp._count_ptr)++;
			_count_ptr = sp._count_ptr;
		}
		~Shared_ptr()
		{
			(*_count_ptr)--;
			if (*_count_ptr == 0)
			{
				delete _ptr;
				delete _count_ptr;
			}
			cout << ues_count() << endl;
				
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		int ues_count()
		{
			return *_count_ptr;
		}
	private:
		T* _ptr;
		int* _count_ptr;
		//static int _count;
	};
}
//template<class T>
//int bit1::Shared_ptr<T>::_count = 0;

int main()
{
	bit1::Shared_ptr<int> sp1(new int(1));
	bit1::Shared_ptr<int> sp2 = sp1;
	bit1::Shared_ptr<int> sp21 = sp1;
	bit1::Shared_ptr<int> sp211 = sp1;
	bit1::Shared_ptr<int> sp2111 = sp1;
	cout << sp1.ues_count() << endl;
	cout << sp1.operator->() << endl;
	return 0;
}

除了上面的操作,还有一个最为重要的赋值操作, 如下:

复制代码
bit1::Shared_ptr<T>& operator=(bit1::Shared_ptr<T>& sp)
		{
			//防止自己给自己赋值
			if (_ptr != sp._ptr)
			{
				//需要释放原来的,以防内存泄漏, sp1 = sp3,sp1引用计数为1,如果直接赋值,会导致原本的sp1管理的资源没有释放
			//resele();
				this->~Shared_ptr();
				_ptr = sp._ptr;
				(*sp._count_ptr)++;
				_count_ptr = sp._count_ptr;
			}
			
			return *this;
		}

这个写法可以防止自己给自己赋值,也可以防止他人赋值造成的内存泄漏问题。

shared_ptr的缺点

以节点为例子

复制代码
template<class T>
	struct ListNode
	{
	public:
		ListNode(const T& val)
			:_val(val)
			, _next(nullptr)
			, _prevl(nullptr)
		{

		}
	//private:
		T _val;
	//这里不用自定义类型对象, 使用自己定义的Shared_ptr或者官方的
		/*struct ListNode<T>* _next;
		ListNode<T>* _prevl;*/
		bit1::Shared_ptr<bit1::ListNode<T>> _next;
		bit1::Shared_ptr<bit1::ListNode<T>> _prevl;
	};
int main()
{
	bit1::Shared_ptr<bit1::ListNode<int>>n1 = new bit1::ListNode<int>(2);
	bit1::Shared_ptr<bit1::ListNode<int>>n2 = new bit1::ListNode<int>(3);
	n1->_next = n2;
	n2->_prevl = n1;
	return 0;
}

结果如下:

可以看出,对应的引用计数为2,但是我们理想的就是引用计数为1啊,为啥会为2,因为代码

n1->_next = n2;

n2->_prevl = n1;

这里导致引用计数进行增加了,但是为啥会是缺点呢?因为这里会导致内存泄漏,

原理如下:当执行到main函数结束时, 对应的节点1和节点二的引用计数会变成1,因为节点1的_next成员管理节点2,而节点2的_pref成员管理节点1.要想节点1的_next释放,必须节点1释放,而要想节点1释放,又必须节点2的_pref先释放,要想节点2的_pref先释放,又必须节点2释放,而节点2释放就需要节点1的_next释放 这里就成了一个循环了,这个叫循环引用

weak_ptr

这个指针就是为了解决上面的循环引 用创建出来的,使用这个不能增加引用计数,这也就可以避免循环引用了,使用的方法如下:

可以看出,这个weak_ptr构造要么是无参数要么是拷贝构造,所以我们使用这个weak_ptr可以先不构造,

复制代码
template<class T>
	struct ListNode
	{
	public:
		ListNode(const T& val)//右值引用
			:_val(val)
		{

		}
	//private:
		T _val;
	//这里不用自定义类型对象, 使用自己定义的Shared_ptr或者官方的
		/*struct ListNode<T>* _next;
		ListNode<T>* _prevl;*/
		/*bit1::Shared_ptr<bit1::ListNode<T>> _next;
		bit1::Shared_ptr<bit1::ListNode<T>> _prevl;*/
		//这里使用weak_ptr,不增加引用计算
		std::weak_ptr<bit1::ListNode<T>> _next;
		std::weak_ptr<bit1::ListNode<T>> _pret;
	};
	//shared_ptr是官方的 
	int main()
	{
		std::shared_ptr<bit1::ListNode<int>> n1 (new bit1::ListNode<int>(2));
		std::shared_ptr<bit1::ListNode<int>> n2 (new bit1::ListNode<int>(3));
		n1->_next = n2;
		n2->_pret = n1;
		cout << n1.use_count() << endl;
		cout << n2.use_count() << endl;
		return 0;
	}

模拟实现一下:

复制代码
template<class T>
	class Weak_ptr
	{
		//不支持RAll,不参与资源管理
	public:
		Weak_ptr()
			:_val(nullptr)
		{

		}
		Weak_ptr(const bit1::Shared_ptr<T>& ptr)
		{
			_val = ptr.get();
		}
		T operator*()
		{
			return *_val;
		}
		T* operator->()
		{
			return _val;
		}
		Weak_ptr<T>& operator=(const bit1::Shared_ptr<T>& ptr)
		{
			_val = ptr.get();
			return *this;
		}
	private:
		T* _val;


	};

weak_ptr不参与资源管理,但是可以像指针一样使用。

智能指针管理数组的方式错误

复制代码
	std::shared_ptr<bit1::ListNode<int>> n3 (new bit1::ListNode<int>[3]);

这里进行会在释放的时候出现问题,主要就是new出来的地址实际会多出四个字节,会导致释放的时候会部分释放。

在现在的c++17编译器中会出现如下的情况:

复制代码
std::shared_ptr<bit1::ListNode<int>[]> n3 (new bit1::ListNode<int>[3]);

编译器会把bit1::ListNode[]识别出bit1::ListNode*.底层就会释放的时候调用delete[],

如果不想使用上面的,还有其他的方式.标准库中的shared_ptr还构造函数重载了类似的 ,如下:

传入一个定制删除器 ,也就是delete的对象,也就是可调用的对象比如仿函数、函数指针等,如下:

复制代码
template<class T>
	class DetaleArray
	{
	public:
		void operator()(T *ptr)
		{
			cout << "释放成功" << endl;
			delete[] ptr;
		}
	};
std::shared_ptr<bit1::ListNode<int>> n3 (new bit1::ListNode<int>[3], DetaleArray<bit1::ListNode<int>>());
	std::shared_ptr<FILE> n4(fopen("text.txt", "r"), [](FILE* ptr)
		{
			cout << "关闭文件" << endl;
			fclose(ptr);
		});

如果要模拟出这个shared_ptr的话,需要使用到C++11的包装器。

复制代码
template<class T>
	class Shared_ptr
	{
	public:
		Shared_ptr()
		{
			_ptr = nullptr;
			_count_ptr = new int(1);
		}
		template<class R>
		Shared_ptr(T* ptr, R ptr_1)
		{
			_ptr = ptr;
			_count_ptr = new int(1);
			_del = ptr_1;
		}
		Shared_ptr(T* ptr)
		{
			_ptr = ptr;
			_count_ptr = new int(1);
			_del = [](T* ptr_)
			{
				delete ptr_;
			};
		}
		Shared_ptr(Shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			(*sp._count_ptr)++;
			_count_ptr = sp._count_ptr;
		}
		~Shared_ptr()
		{
			resele();
			cout << ues_count() << endl;

		}
		void resele()
		{
			(*_count_ptr)--;
			if (*_count_ptr == 0)
			{
				//delete _ptr;
				_del(_ptr);
				delete _count_ptr;
			}
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T* get() const
		{
			return _ptr;
		}
		int ues_count()
		{
			return *_count_ptr;
		}
		bit1::Shared_ptr<T>& operator=(bit1::Shared_ptr<T>& sp)
		{
			//防止自己给自己赋值
			if (_ptr != sp._ptr)
			{
				//需要释放原来的,以防内存泄漏, sp1 = sp3,sp1引用计数为1,如果直接赋值,会导致原本的sp1管理的资源没有释放
			//resele();
				this->~Shared_ptr();
				_ptr = sp._ptr;
				(*sp._count_ptr)++;
				_count_ptr = sp._count_ptr;
			}

			return *this;
		}
	private:
		T* _ptr;
		int* _count_ptr;
		std::function<void(T*)> _del;
		//static int _count;
	};
	
	template<class T>
	struct ListNode
	{
	public:
		ListNode(const T& val)//右值引用
			:_val(val)
		{

		}
		ListNode()
			:_val(T(0))
		{

		}
	//private:
		T _val;
	//这里不用自定义类型对象, 使用自己定义的Shared_ptr或者官方的
		/*struct ListNode<T>* _next;
		ListNode<T>* _prevl;*/
		/*bit1::Shared_ptr<bit1::ListNode<T>> _next;
		bit1::Shared_ptr<bit1::ListNode<T>> _prevl;*/
		//这里使用weak_ptr,不增加引用计算
		std::weak_ptr<bit1::ListNode<T>> _next;
		std::weak_ptr<bit1::ListNode<T>> _pret;
	};
	
	template<class T>
	class DetaleArray
	{
	public:
		void operator()(T *ptr)
		{
			cout << "释放成功" << endl;
			delete[] ptr;
		}
	};
	int main()
{
	bit1::Shared_ptr<bit1::ListNode<int>> n1(new bit1::ListNode<int>(1));
	bit1::Shared_ptr<bit1::ListNode<int>> n2(new bit1::ListNode<int>[5],bit1::DetaleArray<bit1::ListNode<int>>());
	return 0;
}

也就是增加一个类成员,让其可以符合前面的shared_ptr

特殊类设计

关于私有化构造函数

假如我们创建一个类,这个类在实例化的时候就必须在堆上 ,如果构造函数公有就不能实现这个, 如果私有化可以如下:

实现方式:

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

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

    class HeadOnly
    {
    public:
    template<class ...Arc>
    static HeadOnly* Createobj(Arc&&... Ar)
    {
    return new HeadOnly(std::forward<Arc>(Ar)...);
    }
    //禁用赋值和拷贝
    void operator=(const HeadOnly& he) = delete;
    HeadOnly(const HeadOnly& he) = delete;
    private:
    //void operator=(const HeadOnly& he)
    //{
    // _x = he._x;
    // _y = he._y;
    //}
    HeadOnly()
    {
    _x = 1;
    _y = 1;
    }
    HeadOnly(int x, int y)
    {
    _x = x;
    _y = y;
    }
    //HeadOnly(const HeadOnly& he)
    //{
    // _x = he._x;
    // _y = he._y;
    //}
    int _x;
    int _y;
    };
    int main()
    {
    HeadOnly* ptr= HeadOnly::Createobj();
    HeadOnly* ptr1 = HeadOnly::Createobj(1,2);
    //HeadOnly* ptr2 = HeadOnly::Createobj(*ptr1);
    return 0;
    }

这样不仅可以让赋值和拷贝无法使用,还可以让其在构造的时候私有化,进而达到堆创建的效果。

或者是借助私有构造函数,实现只能在栈上创建对象

方法:

同上将构造函数私有化,然后设计静态方法创建对象返回,然后需要启用拷贝构造

复制代码
class HeadOnly
{
public:
	template<class ...Arc>
	static  HeadOnly Createobj(Arc&&... Ar)
	{
		//return new HeadOnly(std::forward<Arc>(Ar)...);
		return HeadOnly(std::forward<Arc>(Ar)...);
	}
		//禁用赋值和拷贝
	//void operator=(const HeadOnly& he) = delete;
	//HeadOnly(const HeadOnly& he) = delete;
	HeadOnly(const HeadOnly& he)
	{
		_x = he._x;
		_y = he._y;
	}
	void operator=(const HeadOnly& he)
	{
		_x = he._x;
		_y = he._y;
	}
private:

	HeadOnly()
	{
		_x = 1;
		_y = 1;
	}
	HeadOnly(int x, int y)
	{
		_x = x;
		_y = y;
	}
	
	int _x;
	int _y;
};
int main()
{
	HeadOnly ptr= HeadOnly::Createobj();
	HeadOnly ptr1 = HeadOnly::Createobj(1,2);
	HeadOnly* ptr2 = new HeadOnly(ptr);//漏洞
	//HeadOnly* ptr2 = HeadOnly::Createobj(*ptr1);
	return 0;
}

这里就出现一个漏洞,这个new出来的是堆上的,我们再次重载一个new,这样就不会调用全局的new了,如下:

复制代码
	void* operator new(size_t size) = delete;
	void operator delete(void* ptr) = delete;

就无法使用new。

禁用(私有)析构函数

在一些情况下,也会有一些特殊的场景,比如析构函数不能正常使用等,代码如下:

复制代码
class Newclass
{
public:
	
	void Destroy()
	{
		delete this;
	}
private:
	~Newclass()
	{
		cout << "ssss" << endl;
	}
	int _x;
	int _y;
};
int main()
{
	Newclass on;
	return 0;  
}

如图:

这行代码会自动调用析构函数,如果更换为:

复制代码
Newclass* ptr = new Newclass;

是不会自动调用析构函数的,但是这样总不能不释放吧,就会创建一个释放成员函数,释放this。进而在内部调用对应的析构函数,如果把这个类让智能指针进行管理,如下:

复制代码
	std::shared_ptr<Newclass> ptr(new Newclass, [](Newclass* ptr_1)
		{
			ptr_1->Destroy();
		});

需要写一个定制删除器,因为底层是调用delete ,因为这个类已经私有析构函数,所以只能在内部进行调用

不能被继承的类

方法:C++98私有化构造函数 ,C++11使用final关键字

单例模式

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

实现方式:

  1. 构造私有化
  2. 拷贝构造和赋值必须封禁
    一般分为饿汉和懒汉

饿汉

饿汉:在main之前创建出来,不存在多线程

复制代码
class Singleton
{
public:
	static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	{
		return _sing;//返回的可以访问私有成员
	}
private:
	Singleton(int x = 0, int y = 0,vector<string>vstr = {"sssss","wwwwww"})
		:_x(x)
		,_y(y)
		,_vstr(vstr)
	{

	}

	int _x;
	int _y;
	vector<string> _vstr;
	static Singleton _sing;//静态成员,存在静态区,不存在对象中,不会造成嵌套
};
Singleton Singleton::_sing(1, 1, { "ss","ww" });//在main函数之前创建出
int main()
{
	Singleton::Creadeobj();
	return 0;
}

需要注意的是,static Singleton _sing ;是静态成员,存在静态区,不存在对象中,不会造成嵌套

饿汉的缺点:

  1. 如果一个进程有多个单例对象,就会在初始化增加成本,影响应用速度。

  2. 如果多个单例有初始化依赖关系,饿汉无法保证哪个先初始化。

懒汉

就是在main里面进行创建出。C++11之前的写法:

复制代码
class Singleton
{
public:
	//饿汉模式
	//static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	//{
	//	return _sing;//返回的可以访问私有成员
	//}
	//懒汉模式
	static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	{
		if(_ptrsing == nullptr)
			_ptrsing = new Singleton;
	}
private:
	Singleton(int x = 0, int y = 0,vector<string>vstr = {"sssss","wwwwww"})
		:_x(x)
		,_y(y)
		,_vstr(vstr)
	{

	}

	int _x;
	int _y;
	vector<string> _vstr;
	static Singleton _sing;//静态成员,存在静态区,不存在对象中,不会造成嵌套
	static Singleton *_ptrsing;
};
Singleton Singleton::_sing(1, 1, { "ss","ww" });//在main函数之前创建出
Singleton* Singleton::_ptrsing = nullptr;

懒汉显示释放

调用只是初始化一次,但是也会面临内存泄漏的危险,所以会增加显示释放的方式,如下:

复制代码
class Singleton
{
public:
	//饿汉模式
	//static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	//{
	//	return _sing;//返回的可以访问私有成员
	//}
	//懒汉模式
	static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	{
		if(_ptrsing == nullptr)
			_ptrsing = new Singleton;
		return *_ptrsing;
	}
	static void Delsingleton()
	{
		if (_ptrsing)
		{
			delete _ptrsing;
			_ptrsing = nullptr;
		}
	}
	void Sprintptr()
	{
		cout << _ptrsing << endl;
	}
private:
	Singleton(int x = 0, int y = 0,vector<string>vstr = {"sssss","wwwwww"})
		:_x(x)
		,_y(y)
		,_vstr(vstr)
	{

	}
	~Singleton()
	{
		cout << "delete" << endl;
	}
	int _x;
	int _y;
	vector<string> _vstr;
	static Singleton _sing;//静态成员,存在静态区,不存在对象中,不会造成嵌套
	static Singleton *_ptrsing;
};
Singleton Singleton::_sing(1, 1, { "ss","ww" });//在main函数之前创建出
Singleton* Singleton::_ptrsing = nullptr;
int main()
{
	Singleton::Creadeobj().Sprintptr();
	Singleton::Delsingleton();//显示释放

	return 0;
}

懒汉内部类释放

但是一般情况下,这个方式还不是最好的,一些人会在这个类里面定义 一个内部类,达到自动析构的方式。如下:

复制代码
class Singleton
{
public:
	//饿汉模式
	//static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	//{
	//	return _sing;//返回的可以访问私有成员
	//}
	//懒汉模式
	static Singleton& Creadeobj()//添加static,只是为让这个函数可以全局调用
	{
		if(_ptrsing == nullptr)
			_ptrsing = new Singleton;
		return *_ptrsing;
	}
	static void Delsingleton()
	{
		if (_ptrsing)
		{
			delete _ptrsing;
			_ptrsing = nullptr;
			cout << "调用了" << endl;
		}
	}
	void Sprintptr()
	{
		cout << _ptrsing << endl;
	}
	//内部类
	class Gc
	{
	public:
		Gc()
		{
			Singleton::Delsingleton();
		}
	};
private:
	Singleton(int x = 0, int y = 0,vector<string>vstr = {"sssss","wwwwww"})
		:_x(x)
		,_y(y)
		,_vstr(vstr)
	{

	}
	~Singleton()
	{
		cout << "delete" << endl;
	}
	
	int _x;
	int _y;
	vector<string> _vstr;
	static Singleton _sing;//静态成员,存在静态区,不存在对象中,不会造成嵌套
	static Singleton *_ptrsing;
	static Singleton::Gc gc;
};
Singleton Singleton::_sing(1, 1, { "ss","ww" });//在main函数之前创建出
Singleton* Singleton::_ptrsing = nullptr;
Singleton::Gc Singleton::gc;

int main()
{
	Singleton::Creadeobj().Sprintptr();
	//Singleton::Delsingleton();//显示释放
	return 0;
}

上面的写法是C++11之前的写法,而关于C++11懒汉的写法:

复制代码
//懒汉模式C++11之后的写法
	static Singleton& Creadeobj()
	{
		static Singleton ptrsing;
		return ptrsing;
	}

这样写在C++11之后不会有线程安全

相关推荐
lsx2024065 小时前
MySQL 处理重复数据
开发语言
一水鉴天5 小时前
整体设计 定稿 之23+ dashboard.html 增加三层次动态记录体系仪表盘 之2 程序 (Q199 之2) (codebuddy)
开发语言·前端·javascript
艾上编程5 小时前
《Python实战小课:爬虫工具场景——开启数据抓取之旅》导读
开发语言·爬虫·python
再__努力1点5 小时前
【68】颜色直方图详解与Python实现
开发语言·图像处理·人工智能·python·算法·计算机视觉
Jinkxs5 小时前
Java 架构 02:DDD 领域模型设计实战(限界上下文划分)
java·开发语言·架构
code bean5 小时前
【CMake 】[第十篇]CMake find_package 完全指南:让第三方库集成变得简单
c++·cmake
IT19956 小时前
C++使用“长度前缀法”解决TCP“粘包 / 拆包”问题
服务器·网络·c++·tcp/ip
Tipriest_6 小时前
旋转矩阵,齐次变换矩阵,欧拉角,四元数等相互转换的常用代码C++ Python
c++·python·矩阵
毕设源码-钟学长6 小时前
【开题答辩全过程】以 基于PHP的家常菜谱教程网站为例,包含答辩的问题和答案
开发语言·php