实用指南:C++智能指针 -- 通俗易懂&全面的快速讲解

目录

一、为什么学习智能指针

二、引入智能指针

三、智能指针的基本用法 & auto_ptr 

1、智能指针的第一个用处,自动释放内存空间

2、智能指针的第二个用处,可以像普通指针一样使用

3、智能指针的三个常用函数

auto_ptr被淘汰的原因

四、unique_ptr

五、计数智能指针 shared_ptr 

六、shared_ptr的使用陷阱

七、weak_ptr

八、weak_ptr 的特殊函数 -- expired( )

九、完结


一、为什么学习智能指针

    有的智能指针可以在生命周期结束时可以自动释放申请的内存,这就避免了内存泄漏的问题。

    有的智能指针通过计数来自动释放申请的内存,避免内存泄漏。(不懂计数下面会讲)


二、引入智能指针

    智能指针分为四个,它们分别是auto_ptrunique_ptrshared_ptrweak_ptr

    看似很难很多,但是十分简单。

    它们共用一个头文件,如下->:

#include

三、智能指针的基本用法 & auto_ptr 

    在掌握一种智能指针后,其他的智能指针基本都是照搬

1、智能指针的第一个用处,自动释放内存空间

    智能指针的用法如下->:

 T为类型
auto_ptr p1;
auto_ptr p1(new T);

    使用方法如下->:

class A
{
};
int main()
{
	auto_ptr p1;
	auto_ptr p2(new A());
}

    我们上面提到过,智能指针会自动释放内存,我们验证一下,看看是否会自动调用析构函数

    可以看到,智能指针确实会自动释放内存


    智能指针除了自动释放内存还有其他用处吗?

2、智能指针的第二个用处,可以像普通指针一样使用

    智能指针可以调用类中的公开的函数或者对象,如下->:

    智能指针之所以可以像普通指针一样使用,是因为它重载了 ->*

3、智能指针的三个常用函数

    1、get( ) -- 获取指针管理的内存空间

class A
{
public:
	A() { cout << " 构造 " << endl; }
	~A() { cout << " 析构 " << endl; }
};
int main()
{
	auto_ptr p1;//没有管理的内存,不调用析构
	auto_ptr p2(new A());
	A* tep = p2.get();
}

    当然也可以用p1.get( ),来取出这块内存空间的地址并打印出来


    2、release( ) -- 取消智能指针对动态内存的托管,但不会主动释放内存

    这里我们可以看到并没有调用析构函数,说明智能指针失去了对这块空间的管理,就不会自动调用析构函数了


    3、reset( ) -- 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

    可以看到,reset确实是这样的


auto_ptr 的注意事项

    auto_ptr在使用时,最好不要使用复制 或者 赋值运算符(=),因为复制或者赋值都会改变资源的所有权

    我们代码举例如下->:

#define _CRT_SECURE_NO_WARNINGS
#include
#include
using namespace std;
class A
{
public:
	A() { cout << " 构造 " << endl; }
	~A() { cout << " 析构 " << endl; }
	void get(){ cout << " get() " << endl; }
	int a;
};
int main()
{
	auto_ptr p1;//没有管理的内存,不调用析构
	auto_ptr p2(new A());
	cout << " 原地址 " << endl;
	cout << p1.get() << endl;
	cout << p2.get() << endl;
	cout << endl;
	cout << " 赋值后的地址 " << endl;
	p1 = p2;
	cout << p1.get() << endl;
	cout << p2.get() << endl;
}

    本来我们的p2是有内存的,但是赋值给p1后,p1接管了p2管理的内存,p2本身放弃了这个内存块的所有权

复制也是同理


    在 auto_ptr 中,不支持对象数组的内存管理

    但在 unique_ptr,shared_ptr 支持

	auto_ptr p1(new int[4]);//不支持,编译错误
    auto_ptr p1(new int[4]);//支持,但调用的是delete,会内存泄漏
	//以下两个智能指针下面会讲到
	unique_ptr p2(new int[4]);
	shared_ptr p3(new int[4]);

auto_ptr被淘汰的原因

    auto_ptr 已经被淘汰了现在,原因有两个

1、 首先就是我们刚才所提到的,复制 或者 赋值 会失去对内存空间的管理权。这是一种极其危险的事情。

2、STL容器内的数据必须支持互相之间可以 复制 和 赋值 ,但是 auto_ptr 在STL容器中却不支持

vector> vec;
auto_ptr p3(new string("I'm P3"));
auto_ptr p4(new string("I'm P4"));
// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" <<  *vec.at(0) << endl;
cout << "vec[1]:" <<  *vec[1] << endl;
// 风险来了:
vec[0] = vec[1];	// 如果进行赋值,问题又回到了上面一个问题中。
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;

3、 不支持对象数组的内存管理

	auto_ptr p1(new int[4]);//不支持,编译错误
    auto_ptr p1(new int[4]);//支持,但调用的是delete,会内存泄漏
	//以下两个智能指针下面会讲到
	unique_ptr p2(new int[4]);
	shared_ptr p3(new int[4]);

四、unique_ptr

    unique_ptr 是 auto_ptr 的进化版本,它包含我们在 auto_ptr 中讲解的所有功能如有,如 : 自动释放内存,可以像普通指针使用,可以使用常用的三个函数,不能直接 复制 或者 赋值。使用这些的语法都是一样的

从功能上来讲,唯一不同的就是支持对象数组的内存管理

    我们直接拿出不同点来讲解,很少,很简单。


    本质上来讲,unique_ptr 说是 auto_ptr 的进化版本,其实就是语法更加严格了

    使用 auto_ptr 时,程序员可能不小心赋值出错了,这很严重。

    那么 unique_ptr 就为了限制这种情况,直接不允许编译。

	auto_ptr p1;
	auto_ptr p2(new A());
	p1 = p2;//编译正常,运行正常
	unique_ptr p3;
	unique_ptr p4(new A());
	p3 = p4;		//编译不通过,不允许这样写
//------------------------------------------------------------
	auto_ptr	tep1(p1);//编译允许,运行允许
	unique_ptr tep2(p3);	//编译不通过,不允许这样写

但是严格归严格,并不是完全禁止。

    使用右值转换可以实现 复制 或者 赋值 。

	auto_ptr p1;
	auto_ptr p2(new A());
	p1 = p2;//编译正常,运行正常
	unique_ptr p3;
	unique_ptr p4(new A());
	p3 = move(p4);
//------------------------------------------------------------
	auto_ptr	tep1(p1);//编译允许,运行允许
	unique_ptr tep2(move(p3));

    unique_ptr 进行右值转换后的效果跟 使用auto_ptr执行p1 = p2是一样的,move后的值还是会失去内存空间的管理权,并让新的指针进行管理。所以unique_ptr 只是让程序员明确这样使用的代价,不要误操作。

    我们来验证一下


其次,就是对象数组内存管理的区别

	auto_ptr p1(new int[4]);//auto_ptr 不支持对象数组的内存管理
	unique_ptr p2(new int[4]);//unique_ptr 支持对象数组的内存管理  自动调用 delete[]

对象数组的内存管理可以让我们直接访问下标,如下->:


五、计数智能指针 shared_ptr 

    这个指针不太一样,它是通过计数来决定要不要释放内存空间的。

    什么意思?

use_count( )函数

    我们先认识一个 shared_ptr 的独享函数 use_count( ) ,这个函数可以知道 shared_ptr 这个智能指针管理的计数是多少,代码例子如下->:

    可以看到打印了1,这个1代表的是p1所管理的这块内存空间现在有1个shared_ptr指针管理着

    那么如果再多一个 shared_ptr 指针管理这块内存空间,计数器就会变成2

    为了让其他的 shared_ptr 管理这个内存块,shared_ptr 就会支持赋值运算符(=)

    shared_ptr 的赋值运算符的意思是共享所有权,因此不会失去对这块内存空间的控制

    我们代码验证如下->:

    可以看到,计数确实变成了2

    这代表有2个 shared_ptr 的智能指针在管理这块内存空间

    注意,我说的是 shared_ptr 在管理这块内存空间,不是普通指针普通指针是不会改变计数的,原因我们下面讲

    可以看到,普通指针确实不会改变计数,这是因为->:

    shared_ptr 是根据计数来决定要不要释放内存的,当 shared_ptr 的生命周期到了的时候,这个计数就会减 1 。拿我们的代码说明,有一块内存空间被 p1 和 p2 两个智能指针管理,计数为 2,当 p1 的生命周期结束,计数减 1 ,从 2 变为 1 。然后 p2 的生命周期结束,计数减 1 ,从 1 变为 0 。计数一旦变为 0 ,这个内存就自动释放掉了。

    那么为什么普通指针不加计数呢,因为普通指针生命周期结束并不会影响计数器,我们之前学习或者使用普通指针的时候,从不考虑计数的问题。因为普通指针根本就没有计数器。


    需要注意的是,shared_ptr 是一个极其负责的智能指针,因此 shared_ptr 在智能指针的三个常用函数中并不支持 release 函数的使用


shared_ptr 支持主动释放内存,主需要把 shared_ptr 指针置为空,就可以主动释放内存

shared_ptrr up1(new int(10));
up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放
// 或
up1 = NULL; // 作用同上

    一些常见用法,比如支持对象数组内存管理,以及swap交换管理的内存空间

	//注意要加上[],否则调用的是 delete 会内存泄漏
	unique_ptr p1(new A[5]{1,2,3,4,5});
	shared_ptr p2(new A[5]{6,7,8,9,0});
	unique_ptr p3(new A);
	unique_ptr p4(new A);
	swap(p3, p4);//可以交互内部管理的内存空间,shared_ptr 同理,引用计数也会随之改变

六、shared_ptr的使用陷阱

    看下面这段代码,这是一个死循环代码

#include 
#include 
#include 
using namespace std;
class Girl;
class Boy {
public:
	Boy() {cout << "Boy 构造函数" << endl;}
	~Boy() {cout << "~Boy 析构函数" << endl;}
	void setGirlFriend(shared_ptr _girlFriend) {
		this->girlFriend = _girlFriend;
	}
private:
	shared_ptr girlFriend;
};
class Girl {
public:
	Girl() {cout << "Girl 构造函数" << endl;}
	~Girl() {cout << "~Girl 析构函数" << endl;}
	void setBoyFriend(shared_ptr _boyFriend) {
		this->boyFriend = _boyFriend;
	}
private:
	shared_ptr boyFriend;
};
void useTrap() {
	shared_ptr spBoy(new Boy());
	shared_ptr spGirl(new Girl());
	// 陷阱用法
	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
	// 此时boy和girl的引用计数都是2
}
int main(void) {
	useTrap();
	system("pause");
	return 0;
}

    spboy 和 grilfriend 指向同一块空间,spgirl 和 boyfriend 指向同一块空间
    spboy 释放了,所以 spboy 和 girlfriend 的计数都变为 1
    spgirl 再释放,spgirl和boyfriend计数也都为 1
    最后都为 1,谁都没释放掉这块空间

所以在使用 shared_ptr 智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!

    只要注释掉其中一个,就可以正常释放了

void useTrap() {
	shared_ptr spBoy(new Boy());
	shared_ptr spGirl(new Girl());
	// 单方获得管理
	//spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
}

    1、首先释放 spBoy ,但是因为girl对象里面的智能指针还托管着 boy ,boy 的引用计数为2,所以释放spBoy时,引用计数减1,boy 的引用计数为 1 ;
    2、在释放 spGirl ,girl 的引用计数减 1,为零,开始释放 girl 的内存,因为 girl 里面还包含有托管 boy 的智能指针对象,所以也会进行 boyFriend 的内存释放,boy 的引用计数减 1,为零,接着开始释放 boy 的内存。最终所有的内存都释放了。


七、weak_ptr

    1、weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造

    2、它的构造和析构不会引起引用记数的增加或减少

    3、同时 weak_ptr 没有重载 * 和 -> 但可以使用 lock( ) 获得一个可用的 shared_ptr 对象

weak_ptr p1(new int);//编译错误,weak_ptr 只可以从 weak_ptr 或者 shared_ptr 构造
shared_ptr p2(new A());
weak_ptr p3(p2);
weak_ptr p4(p3);

    可以看到计数还是为1,weak_ptr 的构造或者析构是不影响计数的


    weak_ptr 没有重载 * 和 ->,但 weak_ptr 支持lock函数,这个函数可以返回一个 shared_ptr 对象


    实战使用 ->: 刚才 shared_ptr 的使用陷阱,使用 weak_ptr 解决

#include 
#include 
#include 
using namespace std;
class Girl;
class Boy {
public:
	Boy() {cout << "Boy 构造函数" << endl;}
    ~Boy() {cout << "~Boy 析构函数" << endl;}
    void setGirlFriend(shared_ptr _girlFriend) {
		this->girlFriend = _girlFriend;
		// 在必要的使用可以转换成共享指针
		shared_ptr sp_girl;
		sp_girl = this->girlFriend.lock();
		cout << sp_girl.use_count() << endl;
		// 使用完之后,再将共享指针置NULL即可
		sp_girl = NULL;
	}
private:
	weak_ptr girlFriend;
};
class Girl {
public:
	Girl() {cout << "Girl 构造函数" << endl;}
    ~Girl() {cout << "~Girl 析构函数" << endl;}
    void setBoyFriend(shared_ptr _boyFriend) {
		this->boyFriend = _boyFriend;
	}
private:
	shared_ptr boyFriend;
};
void useTrap() {
	shared_ptr spBoy(new Boy());
	shared_ptr spGirl(new Girl());
	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
}
int main(void) {
	useTrap();
	system("pause");
	return 0;
}

八、weak_ptr 的特殊函数 -- expired( )

expired( ):判断当前 weak_ptr 智能指针是否还有托管的对象,有则返回 false,无则返回 true


九、完结

posted @ 2026-01-16 13:46  gccbuaa  阅读(0)  评论(0)    收藏  举报