C++智能指针

发布于:2025-05-24 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

 

内存泄漏问题初窥 

auto_ptr

unque_ptr

shared_ptr

weak_ptr


 

内存泄漏问题初窥
 

#include <iostream>
#include <stdexcept>

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

void Func() {
    int* p1 = new int;
    int* p2 = new int;
    std::cout << div() << std::endl;
    delete p1;
    delete p2;
}

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

Func  函数中, new  操作或 div  函数抛异常,会使 p1 、 p2  指向内存无法释放,造成内存泄漏。


内存泄漏详解
 
定义与危害
 
内存泄漏指程序分配内存后,因疏忽或错误未释放不再使用的内存,造成内存浪费。长期运行程序出现内存泄漏,会致响应变慢、最终卡死。
 
分类
 
- 堆内存泄漏:用 malloc / new  等分配堆内存,若未用 free / delete  释放,如 int* p = new int;  后无 delete p;  ,会产生堆内存泄漏。
- 系统资源泄漏:程序使用套接字、文件描述符等系统资源,未正确释放,会造成系统资源浪费,影响系统性能。
 
检测方法
 
- Linux: valgrind  可检测内存泄漏,在终端运行 valgrind your_program  分析。
- Windows: VLD  集成到Visual Studio 可检测内存泄漏,调试时自动报告。
 
避免策略
 
- 遵循良好编码规范,但遇异常仍可能泄漏。
- 利用RAII思想,如智能指针:
-  unique_ptr :独占资源,离开作用域自动释放, std::unique_ptr<int> ptr(new int(10));  。
-  shared_ptr :共享资源,引用计数为0时释放, std::shared_ptr<int> ptr1(new int(20));  。
 
智能指针是C++ 避免内存泄漏的有效工具,合理使用能提升程序内存管理的安全性与可靠性。

auto_ptr

源码

#pragma once
#include<iostream>
using namespace std;
namespace ldg
{
	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		// 像指针一样
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{
		}

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

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

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

		// ap3(ap1)
		// 管理权转移
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
	private:
		T* _ptr;
	};

缺陷:拷贝时使指针悬空

unque_ptr

解决了悬空问题,暴力阻止拷贝

源码

template<class T>
class unique_ptr
{
public:
	// RAII
	// 像指针一样
	unique_ptr(T* ptr)
		:_ptr(ptr)
	{
	}

	~unique_ptr()
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

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

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

	// ap3(ap1)
	// 管理权转移
	// 防拷贝
	unique_ptr(unique_ptr<T>& ap) = delete;
	unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
	T* _ptr;
};

对拷贝构造和=重载只声明,同时私有化防止外部定义实现了解决了悬空问题

shared_ptr

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

源码

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 像指针一样
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

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

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

		// sp3(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		// sp1 = sp5
		// sp6 = sp6
		// sp4 = sp5
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr == sp._ptr)
				return *this;

			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}

			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);

			return *this;
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		int* _pcount;
	};


	

std::shared_ptr的线程安全问题
通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分
为两方面:
1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

循环操作缺陷

示例代码

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}

	~A()
	{
		cout << this;
		cout << " ~A()" << endl;
	}
	//private:

	int _a;
};
struct Node
{
	A _val;
	//Node* _next;
	//Node* _prev;

	//bit::shared_ptr<Node> _next;
	//bit::shared_ptr<Node> _prev;

	ldg::weak_ptr<Node> _next;
	ldg::weak_ptr<Node> _prev;
	// weak_ptr不是RAII智能指针,专门用来解决shared_ptr循环引用问题
	// weak_ptr不增加引用计数,可以访问资源,不参与资源释放的管理
};
//
//int main()
//{
//	
//
//	// 循环引用
//	ldg::shared_ptr<Node> sp1(new Node);
//	ldg::shared_ptr<Node> sp2(new Node);
//
//	cout << sp1.use_count() << endl;
//	cout << sp2.use_count() << endl;
//
//	sp1->_next = sp2;
//	sp2->_prev = sp1;
//
//	cout << sp1.use_count() << endl;
//	cout << sp2.use_count() << endl;
//
//	return 0;
//}

解决循环操作缺陷需要weak_ptr;

weak_ptr

	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 shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

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

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

weak_ptr 是C++ 智能指针的一种,其主要作用是解决 shared_ptr 带来的循环引用问题,同时还能用于观察 shared_ptr 管理的对象。以下是具体介绍:
 
解决循环引用
 
当两个或多个 shared_ptr 相互引用时,会导致循环引用,使对象无法被正确释放,造成内存泄漏。 weak_ptr 可以打破这种循环引用。例如,有两个类 A 和 B , A 中有一个 shared_ptr 指向 B , B 中也有一个 shared_ptr 指向 A ,形成循环引用。若将 B 中指向 A 的指针改为 weak_ptr ,则可避免循环引用,使对象在不再被其他 shared_ptr 引用时能正常释放内存。
 
观察 shared_ptr 管理的对象
 
 weak_ptr 可以观察 shared_ptr 所管理的对象,它不影响对象的引用计数。可以通过 lock 函数尝试获取 weak_ptr 所观察对象的 shared_ptr ,如果对象还存在, lock 函数会返回一个有效的 shared_ptr ,否则返回一个空的 shared_ptr 。这在某些需要检查对象是否存在,但又不想增加对象引用计数的场景中很有用。

 


网站公告

今日签到

点亮在社区的每一天
去签到