C++智能指针详解

一、智能指针概念

智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。

二、为什么需要智能指针

下面我们先分析一下下面这段程序有没有什么内存方面的问题?



int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int[10]; // 这里可能会抛异常,此时抛异常内存会分配失败,程序结束后不会造成内存泄漏。
	int* p2 = new int[10]; // 这里可能会抛异常,此时抛异常会导致p2以及后面的内存分配失败,退出程序后,
	                       //由于p1已经成功申请内存,但是C++没有内存回收机制,因此会造成内存泄漏。
	int* p3 = new int[10]; // 这里可能会抛异常

	try
	{
		div();
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;

		throw;
	}

	delete[] p1;
	delete[] p2;
	delete[] p3;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
		// ...
	}

	return 0;
}

关于内存泄漏见文章【C++ 内存管理】

三、智能指针设计原理剖析

RAII思想

RAII(Resource Acquisition Is Initialization),也称为 “资源获取就是初始化” ,是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。RAII是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效

利用RAII思想设计的SmartPtr类

template<class T>
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}

private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

利用RAII的思想,将变量的资源交给一个对象管理,在对象的生命周期结束之时自动释放资源,巧妙地解决了内存泄漏的问题。
但是上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将 * 、-> 重载下,才可让其像指针一样去使用。

template <class T>
class SmartPtr
{
public:
	// RAII思想
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		//delete[] _ptr;
		delete _ptr;
		_ptr = nullptr;
	}

	// 像指针一样
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T* Get()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1, int month = 1, int day = 1)
	{

	}
};

int main()
{
	SmartPtr<int> sp1(new int);
	*sp1 = 10;
	cout << *sp1 << endl;

	SmartPtr<Date> sparray(new Date);
	// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
	// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
	sparray->_year = 2018;
	sparray->_month = 1;
	sparray->_day = 1;
}

总结一下智能指针的原理:

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

四、C++标准库中的智能指针

1. std::auto_ptr

std::auto_ptr文档
C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。

int main()
{
	std::auto_ptr<int> sp1(new int);
	std::auto_ptr<int> sp2(sp1); // 管理权转移

	// sp1悬空
	*sp2 = 10;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
	return 0;
}

当sp2利用sp1进行拷贝构造时,sp1便被置空了,auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理。


namespace lhf
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& p)
			:_ptr(p._ptr)
		{
			p._ptr = nullptr; //管理权转移
		}

		auto_ptr<T>& operator=(auto_ptr<T>& p)
		{
			if (this != &p)
			{
				if (_ptr)
				{
					delete _ptr;
				}

				//资源转移到当前对象
				_ptr = p._ptr;
				p._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				//cout << "delete" << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};
}

2. std::unique_ptr

unique_ptr是C++11提供的。C++11库才更新了智能指针的实现,在C++11出来之前,第三方库boost已经搞好了更好用的scoped_ptr、shared_ptr、weak_ptr等智能指针,C++11将boost库中的智能指针的精华部分吸收了过来,实现了官方库的unique_ptr、shared_ptr、weak_ptr等智能指针。
unique_ptr文档
unique_ptr的实现原理:简单粗暴的防拷贝。下面简化模拟实现了一份unique_ptr来了解它的原理。

namespace lhf
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}

		unique_ptr(const unique_ptr<T>& p) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& p) = delete;
	private:
		T* _ptr;
	};
}
int main()
{
	lhf::unique_ptr<int> sp1(new int);
	lhf::unique_ptr<int> sp2(sp1);

	std::unique_ptr<int> sp1(new int);
	std::unique_ptr<int> sp2(sp1);

 return 0;
}

3. std::shared_ptr

std::shared_ptr文档
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

模拟实现shared_ptr的代码

namespace lhf
{
	template<class T>
	class shared_ptr
	{
	private:
		void release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;

				delete _pCount;
				_pCount = nullptr;
			}
		}
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pCount(new int(1))
		{}

		shared_ptr(const shared_ptr<T>& p)
			:_ptr(p._ptr)
			, _pCount(p._pCount)
		{
			++(* _pCount);
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& p)
		{
			if (_ptr != p._ptr)
			{
				this->release();
				_ptr = p._ptr;
				_pCount = p._pCount;
				++(*_pCount);
			}
			return *this;
		}

		~shared_ptr()
		{
			this->release();
		}

		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}


	private:
		T* _ptr;
		int* _pCount;
	};
}
int main()
{
	 lhf::shared_ptr<int> sp1(new int);
	 lhf::shared_ptr<int> sp2(sp1);
	 lhf::shared_ptr<int> sp3(sp1);

	 lhf::shared_ptr<int> sp4(new int);
	 lhf::shared_ptr<int> sp5(sp4);

	 //sp1 = sp1;
	 //sp1 = sp2;

	 sp1 = sp4;
	 sp2 = sp4;
	 sp3 = sp4;

	 *sp1 = 2;
	 *sp2 = 3;

 return 0;
}

4. std::shared_ptr的循环引用

案例如下:

struct ListNode
{
	int _data;
	shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~ListNode() 
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

循环引用分析:

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。


解决方法:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr。原理就是,node1->_next = node2;和node2->_prev = node1,时weak_ptr的_next和 _prev不会增加node1和node2的引用计数

5. std::weak_ptr

std::weak_ptr 要与 std::shared_ptr 一起使用。 一个 std::weak_ptr 对象看做是 std::shared_ptr 对象管理的资源的观察者,它不影响共享资源的生命周期。

  1. 如果需要使用 weak_ptr 正在观察的资源,可以将 weak_ptr 提升为 shared_ptr。
  2. 当 shared_ptr 管理的资源被释放时,weak_ptr 会自动变成 nullptr。

weak_ptr的模拟实现

namespace lhf
{
	// 不参与指向资源的释放管理
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const lhf::shared_ptr<T>& p)
		{
			_ptr = p._ptr;
			return *this;
		}

	

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	public:
		T* _ptr;
	};
}
struct ListNode
{
	/*ListNode* _next = nullptr;
	ListNode* _prev = nullptr;*/



	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

	int _val = 0;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	std::shared_ptr<ListNode> node1(new ListNode);
	std::shared_ptr<ListNode> node2(new ListNode);

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node1->_next = node2;
	node2->_prev = node1;

	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}


删除器:
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题。

// 仿函数的删除器
template<class T>
struct FreeFunc
{
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};
template<class T>
struct DeleteArrayFunc
{
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};
int main()
{
	FreeFunc<int> freeFunc;
	std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
	DeleteArrayFunc<int> deleteArrayFunc;
	
	std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc); 
	std::shared_ptr<A> sp4(new A[10], [](A* p){delete[] p; });
	std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p){fclose(p); });
	return 0;
}

由此可见,利用删除器就可以使不是new出来的对象或者是不同的类型的对象都可以由智能指针进行管理。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++智能指针详解 智能指针详解 智能指针内容很多,重点是基本⽤法。 #include <boost/shared_ptr.hpp> class CBase: public boost::enable_shared_from_this<CBase> { public: virtual void f(){}//必须有个虚函数才能向上向下转换。 } typedef boost::shared_ptr<CBase> CBasePtr; class CChild: public CBase {} typedef boost::shared_ptr<CChild> CChildPtr; void main() { CBasePtr ptrBase = boost::make_shared<CBase>(); //CBasePtr ptrBase = CBasePtr(new CBase()); // 向下转换 CChildPtr ptrChild = boost::dynamic_pointer_cast<CChild>(ptrBase); // 向上转换 CBasePtr ptrXXX = ptrChild; // 普通转换 CChildPtr ptrXX = CChildPtr(dynamic_cast<CChild*>(ptrXXX.get())); } 暂时学会这些⽤法即可。 url: C++ 智能指针详解 ⼀、简介 由于 C++ 语⾔没有⾃动内存回收机制,程序员每次 new 出来的内存都要⼿动 delete。程序员忘记 delete,流程太复杂,最终导致没 有 delete,异常导致程序过早退出,没有执⾏ delete 的情况并不罕见。 ⽤智能指针便可以有效缓解这类问题,本⽂主要讲解参见的智能指针的⽤法。包 括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、、boost::weak_ptr、boost::intrusive_ptr。你可能会想,如 此多的智能指针就为了解决new、delete匹配问题,真的有必要吗?看完这篇⽂章后,我想你⼼⾥⾃然会有答案。 下⾯就按照顺序讲解如上 7 种智能指针(smart_ptr)。 ⼆、具体使⽤ 1、总括 对于编译器来说,智能指针实际上是⼀个栈对象,并⾮指针类型,在栈对象⽣命期即将结束时,智能指针通过析构函数释放有它管理的 堆内存。所有智能指针都重载了"operator->"操作符,直接返回对象的引⽤,⽤以操作对象。访问智能指针原来的⽅法则使⽤"."操作符。 访问智能指针包含的裸指针则可以⽤ get() 函数。由于智能指针是⼀个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指 针是否为空,需要这样判断:if (my_smart_object.get())。 智能指针包含了 reset() ⽅法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递⼀个对象,则智能指 针会释放当前对象,来管理新传⼊的对象。 我们编写⼀个测试类来辅助分析: class Simple { public: Simple(int param = 0) { number = param; std::cout << "Simple: " << number << std::endl; } ~Simple() { std::cout << "~Simple: " << number << std::endl; } void PrintSomething() { std::cout << "PrintSomething: " << info_extend.c_str() << std::endl; } std::string info_extend; int number; }; 2、std::auto_ptr std::auto_ptr 属于 STL,当然在 namespace std 中,包含头⽂件 #include<memory> 便可以使⽤。std::auto_ptr 能够⽅便的管理单个堆 内存对象。 我们从代码开始分析: void TestAutoPtr() { std::auto_ptr<Simple> my_memory(new Simple(1)); // 创建对象,输出:Simple:1 if (my_memory.get()) { // 判断智能指针是否为空 my_memory->PrintSomething(); // 使⽤ operator-> 调⽤智能指针对象中的函数 my_memory.get()->info_extend = "Addition";
C++智能指针详解 1、概述 我们知道除了静态内存和栈内存外,每个程序还有⼀个内存池,这部分内存被称为⾃由空间或者堆。程序⽤堆来存储动态分配的对象即那些 在程序运⾏时分配的对象,当动态对象不再使⽤时,我们的代码必须显式的销毁它们。 在C++中,动态内存的管理是⽤⼀对运算符完成的:new和delete。 new:在动态内存中为对象分配⼀块空间并返回⼀个指向该对象的指针; delete:指向⼀个动态独享的指针,销毁对象,并释放与之关联的内存。 动态内存管理经常会出现两种问题: (1)⼀种是忘记释放内存,会造成内存泄漏; (2)⼀种是尚有指针引⽤内存的情况下就释放了它,就会产⽣引⽤⾮法内存的指针。 为了更加容易(更加安全)的使⽤动态内存,引⼊了智能指针的概念。智能指针的⾏为类似常规指针,重要的区别是它负责⾃动释放所指向 的对象。 标准库提供的两种智能指针的区别在于管理底层指针的⽅法不同:shared_ptr和unique_ptr。 (1)shared_ptr允许多个指针指向同⼀个对象; (2)unique_ptr则"独占"所指向的对象。 标准库还定义了⼀种名为weak_ptr的伴随类,它是⼀种弱引⽤,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头⽂件 中。 2、auto_ptr (不要使⽤的指针) (1)内部⼤概实现:做成⼀个auto_ptr类,包含原始指针成员。 当auto_ptr类型的对象被释放时,利⽤析构函数,将拥有的原始指针delete掉。 //⼤概长这个样⼦(简化版) template<class T> class auto_ptr {   T* ptr; }; (2)⽰例⽤法: void runGame() { std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 ⼀个怪物 monster1->doSomething();//怪物做某种事 }//runGame函数执⾏完时,monster1被释放,然后它的析构函数也把指向的⼀个怪物释放了 复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。 这说明auto_ptr是独占性的,不允许多个auto_ptr指向同⼀个资源。 void runGame() { std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 ⼀个怪物 monster1->doSomething(); //怪物做某种事 std::auto_ptr<Monster> monster2 = monster1; //转移指针 monster2->doSomething(); //怪物做某种事 monster1->doSomething(); //Oops!monster1智能指针指向了nullptr,运⾏期崩溃。 } 注意: 虽然本⽂简单介绍了auto_ptr。 但是不要⽤auto_ptr! 不要⽤auto_ptr! 虽然它是c++11以前的最原始的智能指针,但是在c++11中已经被弃⽤(使⽤的话会被警告)了。 它的替代品,也就是c++11新智能指针unique_ptr,shared_ptr,weak_ptr。 3、shared_ptr(⼀种强引⽤指针) 多个shared_ptr指向同⼀处资源,当所有shared_ptr都全部释放时,该处资源才释放。 (有某个对象的所有权(访问权,⽣命控制权) 即是 强引⽤,所以shared_ptr是⼀种强引⽤型指针) (1)内部⼤概实现:每次复制,多⼀个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。 当shared计数为0时,则证明所有指向同⼀处资源的shared_ptr们全都释放了,则随即释放该资源(哦,还会释放new出来的 SharedPtrControlBlock)。 //shared计数放在这个结构体⾥⾯,实际上结构体⾥还应该有另⼀个weak计数。下⽂介绍weak_ptr时会解释。 struct SharedPtrControlBlock {   int shared_count; }; //⼤概长这个样⼦(简化版) template<class T> class shared_ptr {   T* ptr;   SharedPtrControlBlock* count; }; (2)⽰例⽤法: void runGame() {   std::shared_ptr<Monster> monster1(new Monster());  //计数加到1  do{ std::shared_ptr<Monster> monster
智能指针C++中起到了管理动态分配的对象内存的作用,它们通过封装原始指针并提供自动释放内存的机制来避免内存泄漏。智能指针可以跟踪对象的引用计数,并在没有引用时自动销毁对象。它们还可以提供异常安全,即在发生异常时能够正确地释放资源。在C++中,智能指针的分类包括unique_ptr、shared_ptr和weak_ptr。其中,unique_ptr表示独占所有权的指针,只能有一个指针可以指向对象;shared_ptr表示共享所有权的指针,可以有多个指针指向同一个对象,并且会自动释放对象内存;weak_ptr是对shared_ptr的一种扩展,它允许访问对象但不会增加引用计数,可以用于解决shared_ptr的循环引用问题。在C++中,使用智能指针能够提高代码的安全性和可维护性,减少内存泄漏的风险。例如,可以使用unique_ptr来管理动态分配的资源,确保在离开作用域时正确释放资源,避免忘记释放内存导致的内存泄漏。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【C++智能指针详解](https://blog.csdn.net/qq_53268869/article/details/124551345)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [c++智能指针详解](https://blog.csdn.net/bitcarmanlee/article/details/124847634)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求知.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值