c++智能指针详解

前言

 

c++程序执行时内存大致分为四个区域

代码区:存放函数体的二进制代码,由操作系统管理

全局区:存放全局变量和静态变量以及常量(const修饰的全局常量和字符串常量)

栈区:由编译器自动分配和释放,存放函数的参数和局部变量等,注意不要返回局部变量的地址,因为跳出作用域,局部变量就会被释放。局部常量存放在栈区

堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

概要

  c++需要程序员自己去管理内存的申请和释放,随着语言的进步,java、python使用了垃圾回收机制去管理内存,不再需要人为管理,相关的虚拟机会自动释放不需要的资源,显得十分智能。而c++采用的解决办法是RAII机制即资源获取即初始化,使用了引用计数的想法,让程序员不用担心内存释放或泄露问题。其核心原理是将引用计数、写时拷贝和类析构函数特性相结合。

c++11中主要有以下智能指针

auto_ptr:

 1 可能会有两个auto指针对象拥有同一块堆内存,由于auto指针没有采用引用计数,在对象脱离作用域时,会自动析构释放内存,这样就使同一块堆内存释放两次,造成内存的重复释放;

  2 两个智能指针对象在进行赋值的时候,内部指针的所有权会被转移,右侧的对象就不再具有此内部指针,这个时候再通过右侧智能指针对象访问内存就会报错。

#include <iostream>
#include<memory>
#include<vector>
using namespace std;
void foo_test(auto_ptr<int>p)
{
	printf("%d\r\n", *p);
}
int main()
{
	auto_ptr<int> p1 = auto_ptr<int>(new int(3));
	foo_test(p1);
	printf("%d\r\n", *p1);//此处引发异常,因为foo_test处p1拷贝给p,使得p1实际已经丧失对堆内存的所有权

	vector<auto_ptr<int>> ary;
	auto_ptr<int> p2(new int(3));
	ary.push_back(p2);
	printf("%d\r\n", *p2);//此处引发异常,同样的道理,容器中存值也是拷贝过去
	return 0;
}

unique_ptr:它是对auto_ptr的一个语义明确,内部其实删除了拷贝构造函数和=运算符重载,使得只能采用move语义来移动,这样使得程序员明确知道被移动的对象已经没有内部指针了。

 shared_ptr:浅拷贝:就是多个指针指向一个内存;深拷贝:就是重写开辟一个内存,将数据复制到内存中,然后将内存赋给左值对象。

此智能指针采用了引用计数计数,引用计数:对堆内存数据的引用次数用一个整数记录,当整数为0时就释放堆内存,这样就能做到自动释放内存的目地。但是它解决不了循环引用的问题,在循环引用时,不能做到内存的释放。循环引用就是俩个类互相有对方的shared_ptr指针,循环引用使得堆内存被引用计数了两次,而由类的析构引用计数只会减1变成1,它不为0堆内存不会释放。

 上图是vs中自己实现的强指针为CStrongPtr,弱指针为 CMyWeakPtr等价于c++中的shared_ptr和weak_ptr,这里用自己实现的智能指针来展示循环引用。单步调试到释放之前由图可以得到两堆内存的m_usedCount都为2。

 上图是跳出作用域,智能指针类析构后,由图可知m_usedCount还为1,堆内存并没有释放。

  weak_ptr:弱指针的加入很好的解决了循环引用的问题,只需要将循环引用的两个类其中一个智能指针由强指针声明为弱指针即可。具体如何实现上代码。

标头.h

#pragma once
class CRefCount
{
public:
	CRefCount() {
		m_nUsedCount = 1;
		m_nWeakCount = 1;
	}

	void incUsed() {
		m_nUsedCount++;
	}

	int decUsed() {
		m_nUsedCount--;
		return m_nUsedCount;
	}

	void incWeak() {
		m_nWeakCount++;
	}

	int decWeak() {
		m_nWeakCount--;

		return m_nWeakCount;
	}

	int getUsed() {
		return m_nUsedCount;
	}

private:
	int m_nUsedCount; //强指针引用次数
	int m_nWeakCount; //弱指针引用次数
};

template<typename T>
class CMySmartPtrBase//智能指针的基类,作提高接口使用
{
public:
	CMySmartPtrBase() {};
	~CMySmartPtrBase() {};

	void destroy() {
		delete m_Ptr;
	}

	void release() {
		if (m_pRef != nullptr && m_pRef->decWeak() == 0) {
			delete m_pRef;
		}
	}

protected:
	T* m_Ptr;
	CRefCount* m_pRef;
};

//强指针类型
template<typename T>
class CMyWeakPtr;
template<typename T>
class CStrongPtr : public CMySmartPtrBase<T>
{
	friend class CMyWeakPtr<T>;
public:
	CStrongPtr() {
		this->m_Ptr = nullptr;
		this->m_pRef = nullptr;
	}

	explicit CStrongPtr(T* p) {
		this->m_Ptr = p;
		this->m_pRef = new CRefCount;
	}

	CStrongPtr(CStrongPtr<T>& obj) {

		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incUsed();
		this->m_pRef = obj.m_pRef;
	}

	CStrongPtr<T>& operator=(CStrongPtr<T>& obj) {

		if (this->m_pRef != nullptr && this->m_pRef->decUsed() == 0) {
			this->destroy();
			this->release();
		}

		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incUsed();
		this->m_pRef = obj.m_pRef;

		return *this;
	}

	CStrongPtr(CMyWeakPtr<T>& obj) {
		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incUsed();
		this->m_pRef = obj.m_pRef;
	}

	~CStrongPtr() {
		if (this->m_pRef != nullptr && this->m_pRef->decUsed() == 0) {
			this->destroy();
			this->release();
		}
	}

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

	T* operator->() {
		return this->m_Ptr;
	}

	T* get() {
		return this->m_Ptr;
	}
};


//弱指针类型
template<typename T>
class CMyWeakPtr : public CMySmartPtrBase<T>
{
public:
	friend class CStrongPtr<T>;

	CMyWeakPtr() {
		this->m_Ptr = nullptr;
		this->m_pRef = nullptr;
	}

	CMyWeakPtr(CStrongPtr<T>& obj) {
		//release();

		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incWeak();
		this->m_pRef = obj.m_pRef;
	}

	CMyWeakPtr<T>& operator = (CStrongPtr<T>& obj) {
		this->release();

		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incWeak();
		this->m_pRef = obj.m_pRef;

		return *this;
	}

	CMyWeakPtr(CMyWeakPtr<T>& obj) {

		this->m_Ptr = obj.m_Ptr;
		obj.m_pRef->incWeak();
		this->m_pRef = obj.m_pRef;
	}

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

	CStrongPtr<T>& lock() {
		if (this->m_pRef == nullptr) {
			return CStrongPtr<T>();
		}

		return CStrongPtr<T>(*this);
	}

	bool IsExpried() {
		if (this->m_pRef == nullptr) {
			return true;
		}

		return this->m_pRef->getUsed() == 0;
	}
};

源.cpp

#include<iostream>
#include"标头.h"
class CSon;

class CTest {
public:

	void set(CStrongPtr<CSon> p2) {
		m_p1 = p2;
	}

	CStrongPtr<CSon> m_p1;
};

class CSon {
public:

	void set(CStrongPtr<CTest> p2) {
		m_p1 = p2;
	}

	CMyWeakPtr<CTest> m_p1;
};

void foo() {
	CTest* father = new CTest();
	CSon* son = new CSon();


	CStrongPtr<CTest> ptrFather(father);
	CStrongPtr<CSon> ptrSon(son);

	father->set(ptrSon);
	son->set(ptrFather);

}

int main()
{
	foo();

	return 0;
}


单步跳过结果展示

未释放前

 

析构释放后 

 总结:跟踪智能指针源码可知

 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值