Smart Pointer
C++没有垃圾回收机制,自己动态分配的内存需要自己释放,否则容易出现内存泄漏的问题,当你使用new
了一块内存,在不使用的时候记得及时delete
,但是,这其实是很难做到的,尤其当你的工程代码量比较大的时候,为了解决内存泄漏的问题,一个比较好的办法就是使用智能指针(smart pointer
)。
智能指针存储指向动态分配(堆)对象指针的类,控制着该对象的生命周期,智能指针也是一个模板类,在定义的时候构造对象,在离开作用域的时候自动销毁分配的对象,内部实现了引用计数机制,每引用一次计数加一,每析构一次计数减一,当引用计数为0的时候释放所指向的堆内存。
C++11提供了3中智能指针,都包含在头文件<memory>中:
std::shared_ptr
,允许多个对象指向同一个对象;std::uniq_ptr
,“独占”所指向的对象;std::weak_ptr
,是一种弱引用,指向std::shared_ptr
所管理的对象;
1、std::shared_ptr
类似vector
,在使用智能指针的时候需要给定它所指向的类型,指针的初始化可以通过构造函数、std::make_shared<T>
、reset()
实现。
#include <iostream>
#include <memory>
int main()
{
std::shared_ptr<int> ptr1(new int(1)); //ptr1指向int类型
std::shared_ptr<int> ptr2(ptr1); //用ptr初始ptr2,此时ptr1指向的对象引用计数是2
std::cout << "ptr1 引用计数:" << ptr1.use_count() << std::endl;
std::shared_ptr<std::string> ptr3 = std::make_shared<std::string>("Hello World"); //ptr3指向string类型
std::cout << "ptr3 is : " << *ptr3 << std::endl;
std::shared_ptr<std::string> ptr4; //ptr4指向string类型
ptr4.reset(new std::string("reset init"));
std::cout << "ptr4 is : " << *ptr4<< std::endl;
return 0;
}
ptr1
的初始化是通过构造函数实现;ptr2
的初始化是通过拷贝构造函数实现;ptr3
的初始化是通过std::make_shared<T>
实现;ptr4
的初始化是通过reset()
实现,传入的是内置指针;
在查看ptr1
所指向对象的引用计数调用了use_count()
函数,当进行拷贝或赋值操作,std::shared_ptr
都会记录有多少个其他的std::shared_ptr
指向相同的对象,每一个std::shared_ptr
调用该函数都能够获取所指向对象的指针个数。
//use_count函数定义
_NODISCARD long use_count() const noexcept { // return use count
return _Rep ? _Rep->_Use_count() : 0;
}
//_Rep定义,指向_Ref_count_base类
_Ref_count_base* _Rep{nullptr};
//Rep调用的_Use_count函数
long _Use_count() const noexcept { // return use count
return static_cast<long>(_Uses); //_Uses代表计数
}
在上面的几种初始化中,推荐使用std::make_shared<T>
方式,对于一个未初始化的智能指针,调用reset()
可以将其初始化,对于一个已经初始化的智能指针,可以调用reset()
释放其所指向对象。
//释放资源
void reset() noexcept { // release resource and convert to empty shared_ptr object
shared_ptr().swap(*this);
}
//指向_Ux类型的_Px对象
template <class _Ux>
void reset(_Ux* _Px) { // release, take ownership of _Px
shared_ptr(_Px).swap(*this);
}
#include <iostream>
#include <memory>
int main()
{
std::shared_ptr<int> ptr1(new int(1)); //ptr1指向int类型
std::shared_ptr<int> ptr2(ptr1); //用ptr初始ptr2,此时ptr1指向的对象引用计数是2
std::cout << "ptr1 引用计数:" << ptr1.use_count() << std::endl;
std::cout << "ptr2 引用计数:" << ptr2.use_count() << std::endl;
ptr1.reset(); //释放ptr1指向的对象,此时只有ptr2指向该对象
std::cout << "rest ptr1 引用计数:" << ptr1.use_count() << std::endl;
std::cout << "ptr2 引用计数:" << ptr2.use_count() << std::endl;
return 0;
}
输出结果:
ptr1 引用计数:2
ptr2 引用计数:2
rest ptr1 引用计数:0
ptr2 引用计数:1
在指针初始化过程中,不能利用内置指针给智能指针赋值,否则编译出错。
std::shared_ptr<int> ptr = new int(8); //无法从“int *”转换为“std::shared_ptr<int>”
即使通过智能指针管理动态分配的对象资源,仍然可以获取其原始指针,调用其get()
函数就可以实现。
_NODISCARD element_type* get() const noexcept { // return pointer to resource
return _Ptr;
}
#include <iostream>
#include <memory>
int main()
{
std::shared_ptr<int> ptr(new int(8));
int* p = ptr.get();
std::cout << "*p is " << *p << std::endl;
return 0;
}
输出结果:
*p is 8
对于自己实现的类对象,通过智能指针管理,可以让目标类继承 std::enable_shared_from_this <T>
,通过shared_from_this()
返回包含本类对象的this
指针。
#include <iostream>
#include <memory>
class A : public std::enable_shared_from_this<A>{
public:
void func()
{
std::cout << " A function" << std::endl;
}
std::shared_ptr<A> getSelf()
{
return shared_from_this();
}
};
int main()
{
std::shared_ptr<A> ptr(new A());
std::shared_ptr<A> p = ptr->getSelf();
p->func();
return 0;
}
使用std::shared_ptr的注意事项:
- 不要使用一个原始指针去初始化多个
std::shared_ptr
对象;
int* p = new int(0);
std::shared_ptr<int> ptr1(p);
std::shared_ptr<int> ptr2(p); //避免这样
- 不要在参数中创建
std::shared_ptr
,可能出现内存泄漏;
function(std::shared_ptr<int>(new int), f());
C++的传参顺序在不同的编译器下顺序可能不一样,如果从左向右顺序传参,先new int
,然后调用f()
,如果调用f()
过程中出现了异常,而std::shared_ptr<int>
还没创建,则new int
就出现了内存泄漏,所以,想要在参数中使用std::shared_ptr<int>
,应该提前创建好。
- 利用
shared_from_this
返回包含this指针的std::shared_ptr
,不要单独将this返回出来。
#include <iostream>
#include <memory>
class A : public std::enable_shared_from_this<A>{
public:
void func()
{
std::cout << " A function" << std::endl;
}
std::shared_ptr<A> getSelf()
{
return std::shared_ptr<A>(this);
}
};
int main()
{
std::shared_ptr<A> ptr(new A()); //第一次利用this构造ptr
std::shared_ptr<A> p = ptr->getSelf();//第二次利用this构造p
p->func();
return 0;
}
利用this指针两次构造std::shared_ptr
,但是这两个std::shared_ptr
之间是没有关系的,当这两个std::shared_ptr
析构的时候就会对this
指针析构两次。
//程序抛出异常的代码处
void _Decwref() noexcept { // decrement weak reference count
if (_MT_DECR(_Weaks) == 0) {
_Delete_this();
}
}
- 避免循环引用
#include <iostream>
#include <memory>
class B;
class A {
public:
~A()
{
std::cout << "A is des" << std::endl;
}
std::shared_ptr<B> aptr;
};
class B {
public:
~B()
{
std::cout << "B is des" << std::endl;
}
std::shared_ptr<A> bptr;
};
int main()
{
std::shared_ptr<A> ptr1(new A());
std::shared_ptr<B> ptr2(new B());
ptr1->aptr = ptr2; //循环引用
ptr2->bptr = ptr1;
std::cout << "A use count is " << ptr1.use_count() << std::endl;
std::cout << "B use count is " << ptr2.use_count() << std::endl;
return 0;
}
这里的类A
与类B
的析构函数都没有被调用,A
与B
的引用计数都是2,离开作用域变成1,永远不会析构,出现了内存泄漏问题。
A use count is 2
B use count is 2
2、std::unique_ptr
std::unique_ptr
是独占型的智能指针,在某个时刻只能有一个std::unique_ptr
指向一个给定对象,当std::unique_ptr
被销毁的时候,它所指向的对象也被销毁,它不像std::shared_ptr
共享所指向对象。
std::unique_ptr<int> ptr1(new int(0));
std::unique_ptr<int> ptr2 = ptr1;//错误,不能复制
std::unique_ptr<int> ptr3 = std::move(ptr1); //可以移动
定义了ptr1
指向int
类型对象,由于std::unique_ptr
是独占类型的指针,所以用ptr1
给ptr2
复制是错误的,但是它支持移动,可以将ptr1
的所有权转移给ptr3
,这样是可以的,移动后,ptr1
不指向任何对象。
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<std::string> ptr1(new std::string("unique_ptr"));
std::cout << "ptr1 is " << *ptr1 << std::endl;
std::unique_ptr<std::string> ptr2 = std::move(ptr1);
std::cout << "ptr2 is " << *ptr2 << std::endl;
return 0;
}
输出结果:
ptr1 is unique_ptr
ptr2 is unique_ptr
在C++11中,没有类似std::make_shared
的初始化方法,但是在C++14中,对于std::unique_ptr
引入了std::make_unique
方法进行初始化。
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<std::string> ptr1(new std::string("unique_ptr"));
std::cout << "ptr1 is " << *ptr1 << std::endl;
std::unique_ptr<std::string> ptr2 = std::make_unique<std::string>("make_unique init!");
std::cout << "ptr2 is " << *ptr2 << std::endl;
return 0;
}
输出结果:
ptr1 is unique_ptr
ptr2 is make_unique init!
3、std::weak_ptr
std::weak_ptr
是弱引用指针,一般主要是配合std::shared_ptr
使用,它不会改变所指向对象引用计数的值,也不管理std::shared_ptr
内部指针,一般只监视std::shared_ptr
的生命周期,std::weak_ptr
没有重载*
与->
符号,因为它不共享指针,也不能操作资源,看似好像没有存在的价值,其实不然,std::weak_ptr
可以用来返回this指针和解决循环引用问题。
std::shared_ptr<int> ptr1(new int(8));
std::weak_ptr<int> wptr(ptr1); //wptr弱共享ptr1
这里wptr
弱共享ptr1
,对于ptr1
所指向的对象,我们不能通过wptr
去访问,因为wptr
只能通过expired()
函数判断其若共享的对象是否存在,如果弱引用的对象存在,std::weak_ptr
中的lock()
函数用来获取一个指向共享对象的std::shared_ptr
。
#include <iostream>
#include <memory>
int main()
{
std::shared_ptr<int> sptr1(new int(8));
std::shared_ptr<int> sptr2(sptr1);
std::weak_ptr<int> uptr1(sptr1);
std::cout << "weak ptr use count is " << uptr1.use_count() << std::endl;
if (uptr1.expired()) //判断资源是否被释放
{
std::cout << "uptr1 is expired" << std::endl;
}
else
{
auto p = uptr1.lock(); //如果没有释放,则获取std::shared_ptr
std::cout << "uptr1 is " << *p << std::endl;
}
sptr1.reset(); //将资源释放掉
sptr2.reset();
if (uptr1.expired()) //此时资源已经释放
{
std::cout << "uptr1 is expired" << std::endl;
}
else
{
auto p = uptr1.lock();
std::cout << "uptr1 is " << *p << std::endl;
}
return 0;
}
- 定义
sptr1
、sptr2
指向同一个对象; - 定义
uptr1
弱共享sptr1
、sptr2
指向的对象; - 通过
uptr1
获取其弱共享对象的引用计数,uptr1.use_count()
; - 调用
uptr1
的expired()
函数判断资源是否释放,如果没有释放,就通过lock()
获取对应的std::shared_ptr
; - 释放资源,再次调用
uptr1
的expired
函数判断资源是否释放,此时弱共享的对象已经释放;
输出结果:
weak ptr use count is 2
uptr1 is 8
uptr1 is expired
expired()
函数的定义:
//可以看出expired函数就是在判断其引用计数是否为0
_NODISCARD bool expired() const noexcept { // return true if resource no longer exists
return this->use_count() == 0;
}
lock()
函数的定义:
//lock函数返回对应std::shared_ptr<T>
_NODISCARD shared_ptr<_Ty> lock() const noexcept { // convert to shared_ptr
shared_ptr<_Ty> _Ret;
(void) _Ret._Construct_from_weak(*this);
return _Ret;
}
在std::shared_ptr
中,可以通过shared_from_this
获取包含this
指针的std::shared_ptr
,这是因为enable_shared_from_this
类中存在一个std::weak_ptr
。
_NODISCARD shared_ptr<_Ty> shared_from_this() { // return shared_ptr
return shared_ptr<_Ty>(_Wptr);
}
std::weak_ptr
还能解决循环引用问题,减少内存泄漏情况的发生。
#include <iostream>
#include <memory>
class B;
class A {
public:
~A()
{
std::cout << "A is des" << std::endl;
}
std::shared_ptr<B> aptr;
};
class B {
public:
~B()
{
std::cout << "B is des" << std::endl;
}
std::weak_ptr<A> bptr; //将bptr定义为指向A类型对象的弱引用
};
int main()
{
std::shared_ptr<A> ptr1(new A()); //ptr1指向A类型对象
std::shared_ptr<B> ptr2(new B()); //ptr2指向B类型对象
ptr1->aptr = ptr2; // aptr指向B对象,此时两个std::shared_ptr指向B类型对象
ptr2->bptr = ptr1; //bptr是弱引用,此时还是只有一个std::shared_ptr指向A类型对象
std::cout << "A use count is " << ptr1.use_count() << std::endl;
std::cout << "B use count is " << ptr2.use_count() << std::endl;
return 0;
}
输出结果:
A use count is 1
B use count is 2
A is des
B is des
由于B
中的bptr
是std::weak_ptr<A>
,所以A
的引用计数是1,当A
退出作用域的时候就析构了,当A
析构后,其成员std::shared_ptr<B> aptr
也析构了,这样只剩下ptr2
指向B
了,此时引用计数为1,当ptr2
离开作用域后,B
就被析构了,此时不会出现内存泄漏。
总结,在实际的项目开发中,根据场景选择合适的智能指针,尽量避免使用new
与delete
。