C++11智能指针( shared_ptr,unique_ptr,weak_ptr 的使用方法与注意事项)


前言

智能指针(Smart Pointer)是存储指向动态分配(堆)对象的类,用于生存期控制,能够确保在离开指针所在作用域时,自动正确地销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数。每使用它一次,内部的引用计数加1,每析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。
C++11 提供了 3 种智能指针:std::shared_ptr,std::uniq_ptr,std::weak_ptr,使用时需要引用 memory 头文件。


一、std::shared_ptr 共享的智能指针

std::shared_ptr使用引用计数,每一个 std::shared_ptr 的拷贝都指向相同的内存。在最后一个 std::shared_ptr 析构的时候,内存才会被释放。。

1.1 std::shared_ptr 的基本用法

1. 初始化

可以通过构造函数、std::make_shared 辅助函数和 reset 方法来初始化 shared_ptr,代码如下:

//=============== std::shared_ptr 的初始化 ==================
	std::shared_ptr<int> p(new int(1));
	std::shared_ptr<int> p2 = p;
	std::shared_ptr<int> ptr;
	ptr.reset(new int(1));
	if (ptr) {
		cout << "shared ptr is initialized.";
	}

我们应该优先使用 std::make_shared 来构造智能指针,因为它更高效。

2. 获取原始指针

当需要获取原始指针时,可以通过 get 方法来返回原始指针,代码如下:

std::shared_ptr<int> ptr(new int(1));
int* p = ptr.get();

3. 指定删除器

智能指针初始化可以指定删除器,代码如下:

void DeleteIntPtr(int* p) {
	delete p;
}
std::shared_ptr<int> p(new int, DeleteIntPtr);

当 p 的引用计数为 0 时,自动调用删除器 DeleteIntPtr 来释放对象的内存。删除器可以是一个 lambda表达式,因此,上面的写法还可以改为:

std::shared_ptr<int> p(new int, [](int* p){delete p;});

当我们用 shared_ptr 管理动态数组时,需要指定删除器,因为 std::shared_ptr 的默认删除器不支持数组对象,代码如下:

std::shared_ptr<int> p(new int[10], [](int* p){delete[] p;});  //指定 delete[]

也可以将 std::default_delate 作为删除器。default_delate 的内部是通过调用 delete 来实现功能的,代码如下:

std::shared_ptr<int> p(new int[10], std::default_delete<int[]>);  

另外,还可以通过封装一个 make_shared_array 方法来让 shared_ptr 支持数组,代码如下:

template<typename T>
shared_ptr<T> make_shared_array(size_t size) {
	return shared_ptr<T>(new T[size], default_delete<T[]>());
}

//test
std::shared_ptr<int> p = make_shared_array<int>(10);

1.2 使用 shared_ptr 需要注意的问题

1)不要用一个原始指针初始化多个 shared_ptr,例如下面这样就是错误的:

int *ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);   //logic error

2) 不要在函数实参中创建 shared_ptr,

function (shared_ptr<int>(new int), g());  //有缺陷

因为 C++ 的函数参数的计算顺序在不同的编译器上调用可能是不一样的。一般是从右到左,但也有可能是从左到右。所以可能的过程是先 new int,然后调用 g(),如果恰好 g() 发生异常,而 shared_ptr 还没有创建,则 int 内存泄漏了,正确的写法应该是先创建智能指针,代码如下:

shared_ptr<int> p(new int());
f(p, g());

3)不要将 this 指针通过 shared_ptr 返回出来,因为 this 本质上是一个裸指针,这样会导致重复析构。通过 shared_from_this 返回 this 指针。

struct A 
{
	shared_ptr<A>GetSelf()
	{
		return shared_ptr<S>(this);   //don't do this;
	}
};

int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf();
	return 0;
}

在这个例子中,由于用同一个指针(this)构造了两个智能指针 sp1 和 sp2,而它们之间是没有任何关系的,在离开作用域之后 this 将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回 this 的shared_ptr 的做法是:让目标类通过派生 std::enable_shared_from_this 类,然后使用基类的成员函数 shared_from_this 来返回 this 的 shared_ptr,示例如下:

class A : public std::enable_shared_from_this<A>
{
	std::shared_ptr<A> GetSelf()
	{
		return shared_from_this();
	}
};
std::shared_ptr<A> spy(new A);
std::shared_ptr<A> p = spy->GetSelf();     //OK
  • 避免循环引用,智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。
struct A;
struct B;

struct A {
	std::shared_ptr<B> bptr;
	~A() {
		cout << "A is deleted!" << endl;
	}
};

struct B {
	std::shared_ptr<A> aptr;
	~B() {
		cout << "B is deleted!" << endl;
	}
};

void testPtr() {
	std::shared_ptr<A> ap(new A);
	std::shared_ptr<B> bp(new B);
	ap->bptr = bp;
	bp->aptr = ap;
}

测试结果是两个指针A 和B都不会被删除,存在内存泄漏。循环引用导致 ap 和 bp 的引用计数为 2,在离开作用域之后,ap 和 bp 的引用计数减为1,并不会为 0,导致两个指针都不会被析构,产生了内存泄漏。解决办法是把 A 和 B 任何一个成员变量改为 weak_ptr。

二、unique_ptr 独占的智能指针

1. unique_ptr 基本用法

1.1 unique_ptr 的移动

unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内容的指针,不允许通过赋值将一个 unique_ptr 赋值给另外一个 unique_ptr。下面用法是错误的:

unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = myPtr;    //错误,不能赋值

unique_ptr 不允许复制,但可以通过函数返回给其他的 unique_ptr,还可以通过 std::move 来转移到其他的 unique_ptr,这样它本身就不再拥有原来指针的所有权了,例如:

unique_ptr<T> myPtr(new T);
unique_ptr<T> myOtherPtr = std::move(myPtr);  //OK
unique_ptr<T> ptr = myPtr;                    //错误,只能移动,不可复制

unique_ptr 不像 shared_ptr 可以通过 make_shared 方法来创建智能指针,C++11 目前还没有提供 make_unique 方法。

1.2 指定数组

unique_ptr 和 shared_ptr 相比,unique_ptr 除了独占这个特点之外,还可以指向一个数组:

std::unique_ptr<int []> ptr(new int[10]);
ptr[9] = 9;

//而下面的写法是不合法的
std::shared_ptr<int []> ptr(new int[10]);    //不合法

1.3 指定删除器

unique_ptr 指定删除器和 shared_ptr 是有差别的,比如:

std::shared_ptr<int> ptr(new int(1), [](int* p){delete p;});   //正确
std::unique_ptr<int> ptr(new int(1), [](int* p){delete p;});   //错误

std::unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器,可以这样写:

std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [](int *p) {delete p;}); 

上面这种写法在 lambda 没有捕获变量的情况下是正确的,如果捕获了变量,则会编译报错:

std::unique_ptr<int, void(*)(int*)> ptr(new int(1), [&](int *p) {delete p;}); //错误,捕获了变量

这是因为 lambda 在没有捕获变量的情况下是可以直接转换为函数指针的,一旦捕获了就无法转换了。如果希望 unique_ptr 的删除器支持 lambda,可以这样写:

std::unique_ptr<int, std::function<void(int*)>> ptr(new int(1), [&](int *p){delete p;});

还可以自定义 unique_ptr 的删除器,比如下面的代码:

#include <memory>
#include <functional>
using namespace std;

struct MyDeleter
{
	void operator()(int* p)
	{
		cout<<"delete"<<endl;
		delete p;
	}
};

int main()
{
	std::unique_ptr<int, MyDeleter> p(new int(1));
	return 0;
}

关于 shared_ptr 和 unique_ptr 的使用场景要根据实际应用需求来选择,如果希望只有一个智能指针管理资源或者管理数组就用 unique_ptr,如果希望多个智能指针管理同一个资源就用 shared_ptr。

三、weak_ptr 弱引用的智能指针

弱引用指针 weak_ptr 是用来监视 shared_ptr 的,不会使引用计数加1,它不管理 shared_ptr 内部的指针,主要是为了监视 shared_ptr 的生命周期,更像是 shared_ptr 的一个助手。weak_ptr 没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过 shared_ptr 获得资源的检测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来监视 shared_ptr 中管理的资源是否存在。weak_ptr 还可以用来返回 this 指针和解决循环引用的问题。

1. weak_ptr 基本用法

1)通过 use_count() 方法获得当前观测资源的引用计数,代码如下:

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout<<wp.use_count()<<endl;    //结果输出1

2)通过 expired() 方法判断所检测的资源是否已经被释放,代码如下:

shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if (wp.expired())
{
	std::cout << "weak_ptr 无效,所监测的智能指针已被释放\n";
}
else
{
	std::cout << "weak_ptr 有效\n";
}

//结果输出: weak_ptr 有效

3)通过 lock 方法来获取所监视的 shared_ptr,代码如下:

std::weak_ptr<int> gw;
void f()
{
	if (gw.expired())
	{
		std::cout << "gw is expired\n";
	}
	else
	{
		auto spt = gw.lock();
		std::cout << *spt << "\n";
	}
}

int main()
{
	{
		auto sp = std::make_shared<int>(42);
		gw = sp;
		f();
	}
	f();
	return 0;
}

//输出如下:
42
gw is expired

2. weak_ptr 返回 this 指针

上面提到不能将 this 指针返回为 shared_ptr,需要通过派生 std::enable_shared_from_this 类,并通过其方法 shared_from_this 来返回智能指针,原因是 std::enable_shared_from_this 类中有一个 weak_ptr,这个 weak_ptr 用来观测 this 智能指针,调用 shared_from_this() 方法时,会调用内部这个 weak_ptr 的 lock() 方法,将所观测的 shared_ptr 返回。再看一下前面的例子:

struct A : public std::enable_shared_from_this<A>
{
	std::shared_ptr<A> GetSelf()
	{
		return shared_from_this();
	}

	~A()
	{
		cout << "A is deleted" << endl;
	}
};

int main()
{
	std::shared_ptr<A> spy(new A);
	std::shared_ptr<A> p = spy->GetSelf();
	return 0;
}

//输出结果: A is deleted

在外面创建 A 对象的智能指针和通过该对象返回 this 的智能指针都是安全的,因为 shared_from_this() 是内部的 weak_ptr 调用 lock() 方法之后返回的智能指针,在离开作用域后,spy 的引用计数减为0,A 对象会被析构,不会出现 A 对象被析构两次的问题。

需要注意的是,获取自身智能指针的函数仅在 shared_ptr 的构造函数被调用之后才能使用,因为 enable_shared_from_this 内部的 weak_ptr 只有通过 shared_ptr 才能构造。

3. weak_ptr 解决循环引用的问题

上面提到因为智能指针的循环引用导致内存泄漏的问题,再看一下其示例:

struct A;
struct B;

struct A {
	std::shared_ptr<B> bptr;
	~A() {
		cout << "A is deleted!" << endl;
	}
};

struct B {
	std::shared_ptr<A> aptr;
	~B() {
		cout << "B is deleted!" << endl;
	}
};

void testPtr() {
	std::shared_ptr<A> ap(new A);
	std::shared_ptr<B> bp(new B);
	ap->bptr = bp;
	bp->aptr = ap;
	std::cout << "ap use count: " << ap.use_count() << endl;
	std::cout << "bp use count: " << bp.use_count() << endl;
}

int main()
{
	testPtr();
	return 0;
}

//输出如下:
ap use count: 2
bp use count: 2

在这个例子中由于循环引用导致 ap 和 bp 的引用计数都为2,离开作用域之后引用计数减为1,不会去删除指针,导致内存泄漏。通过 weak_ptr 就可以解决这个问题,只要将 A 或 B 的任意一个成员变量改为 weak_ptr 即可。

struct A;
struct B;

struct A {
	std::shared_ptr<B> bptr;
	~A() {
		cout << "A is deleted!" << endl;
	}
};

struct B {
	//std::shared_ptr<A> aptr;
	std::weak_ptr<A> aptr;   //改为 weak_ptr
	~B() {
		cout << "B is deleted!" << endl;
	}
};

void testPtr() {
	std::shared_ptr<A> ap(new A);
	std::shared_ptr<B> bp(new B);
	ap->bptr = bp;
	bp->aptr = ap;
	std::cout << "ap use count: " << ap.use_count() << endl;
	std::cout << "bp use count: " << bp.use_count() << endl;
}


//输出结果:
ap use count: 1
bp use count: 2
A is deleted!
B is deleted!

这样在对 B 的成员赋值时,即执行 bp->aptr = ap; 时, 由于 aptr 是 weak_ptr,它并不会增加引用计数,所以 ap 的引用计数仍然是1,在离开作用域之后,ap 的引用计数会减为0,A指针会被析构,析构后其内部的 bptr 的引用计数减为1,然后在离开作用域后 bp 引用计数又从1减为0,B对象也会被析构,不会发生内存泄露。


  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是C++中三种智能指针的实现原理和区别。 首先,我们需要明确一下什么是智能指针智能指针是一种C++中的类,它的行为类似于指针,但是它具有一些额外的功能,比如自动内存管理。智能指针能够自动释放所管理的对象,从而避免内存泄漏和野指针的问题。 下面我们分别介绍shared_ptrunique_ptrweak_ptr三种智能指针的实现原理和区别。 ## shared_ptr shared_ptr是一种引用计数智能指针,它的实现原理是通过使用引用计数来跟踪有多少个shared_ptr对象指向同一个对象。每当一个新的shared_ptr对象指向该对象时,引用计数就会增加1,当一个shared_ptr对象被销毁时,引用计数就会减少1。当引用计数变为0时,就表示没有任何shared_ptr对象指向该对象,此时该对象将被自动销毁。 shared_ptr的优点是可以共享资源,缺点是有可能出现循环引用的问题,导致内存泄漏。为了避免这个问题,C++11中引入了weak_ptr。 ## unique_ptr unique_ptr是一种独占式智能指针,它的实现原理是通过禁止拷贝和赋值来保证同一时间只有一个unique_ptr对象指向一个对象。当一个unique_ptr对象被销毁时,它所管理的对象也将会被销毁。为了更好地支持移动语义,C++11中引入了move语义,使得unique_ptr对象可以被移动而不是被复制。 unique_ptr的优点是可移植性好,可以避免循环引用的问题,缺点是不能共享资源。 ## weak_ptr weak_ptr是一种弱引用智能指针,它的实现原理是与shared_ptr配合使用weak_ptr不会增加引用计数,它只是提供了对所指向对象的一个非拥有性的访问。当所指向的对象被销毁后,weak_ptr将自动失效。 weak_ptr的优点是可以避免循环引用的问题,缺点是不能访问所指向对象的成员变量和成员函数。如果需要访问所指向对象的成员变量和成员函数,需要将weak_ptr转换为shared_ptr。 下面是一个示例代码,展示了shared_ptrunique_ptrweak_ptr使用方式: ```c++ #include <iostream> #include <memory> class A { public: A() { std::cout << "A()" << std::endl; } ~A() { std::cout << "~A()" << std::endl; } void foo() { std::cout << "foo()" << std::endl; } }; int main() { // shared_ptr std::shared_ptr<A> p1(new A()); std::shared_ptr<A> p2(p1); std::cout << "p1.use_count() = " << p1.use_count() << std::endl; std::cout << "p2.use_count() = " << p2.use_count() << std::endl; // unique_ptr std::unique_ptr<A> p3(new A()); // std::unique_ptr<A> p4(p3); // error: copy constructor is deleted std::unique_ptr<A> p5(std::move(p3)); if (p3 == nullptr) { std::cout << "p3 is nullptr" << std::endl; } // weak_ptr std::shared_ptr<A> p6(new A()); std::weak_ptr<A> p7(p6); std::cout << "p7.use_count() = " << p7.use_count() << std::endl; if (auto p8 = p7.lock()) { p8->foo(); } else { std::cout << "p7 is expired" << std::endl; } return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值