最近在整理一些 C++ 智能指针的使用和避坑方面的资料,感兴趣的不妨看看
std::unique_ptr
unique_ptr 它是一种独占资源所有权的指针,unique_ptr 会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的 Resource 对象。
在 C++ 11 的时候,我们可以这么使用 unique_ptr :
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::unique_ptr
std::unique_ptr<Resource> res{ new Resource() };
return 0;
} // res goes out of scope here, and the allocated Resource is destroyed
在 C++14 的时候新加入了 make_unique 函数,我们可以利用它构造一个 unique_ptr 对象(支持数组对象):
#include <memory> // for std::unique_ptr and std::make_unique
#include <iostream>
class Fraction
{
private:
int m_numerator{ 0 };
int m_denominator{ 1 };
public:
Fraction(int numerator = 0, int denominator = 1) :
m_numerator{ numerator }, m_denominator{ denominator }
{
}
friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << '/' << f1.m_denominator;
return out;
}
};
int main()
{
// Create a single dynamically allocated Fraction with numerator 3 and denominator 5
// We can also use automatic type deduction to good effect here
auto f1{ std::make_unique<Fraction>(3, 5) };
std::cout << *f1 << '\n';
// Create a dynamically allocated array of Fractions of length 4
auto f2{ std::make_unique<Fraction[]>(4) };
std::cout << f2[0] << '\n';
return 0;
}
输出:
3/5
0/1
操作 unique_ptr
unique_ptr 删除了 删除了 copy constructor 和 copy assignment operator ,所以在在赋值的时候会使用 move 语义,在转移对象的时候必须使用 move 去转移:
#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
std::unique_ptr<Resource> res1{ new Resource{} }; // Resource created here
std::unique_ptr<Resource> res2{}; // Start as nullptr
std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");
// res2 = res1; // Won't compile: copy assignment is disabled
res2 = std::move(res1); // res2 assumes ownership, res1 is set to null
std::cout << "Ownership transferred\n";
std::cout << "res1 is " << (res1 ? "not null\n" : "null\n");
std::cout << "res2 is " << (res2 ? "not null\n" : "null\n");
return 0;
} // Resource destroyed here when res2 goes out of scope
输出:
Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed
unique_ptr 也重载了 operator*
和 operator->
,operator*
会返回对象的引用, operator->
会返回对象的指针。需要注意的是,unique_ptr 所管理的对象可能发生转移,所以可能是空的,所以用这两个重载方法之前可以通过 if 判空:
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
int main()
{
std::unique_ptr<Resource> res{ new Resource{} };
if (res) // use implicit cast to bool to ensure res contains a Resource
std::cout << *res << '\n'; // print the Resource that res is owning
return 0;
}
输出:
Resource acquired
I am a resource
Resource destroyed
一般的情况,我们使用 unique_ptr 之后就不用管 unique_ptr 所管理的对象释放问题了,但是有时候我们依然想拿回对象的所有权,那么可以使用 release 函数返回 unique_ptr 所管理的对象的指针,并且释放对指针的控制权:
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> uptr(new int(10)); //绑定动态对象
std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權
if(uptr == nullptr)
cout<<"uptr give up *int"<<endl;
int * p = uptr2.release(); //uptr2释放对指针的控制权,返回指针,并将uptr2置为空
if(uptr2 == nullptr)
cout<<"uptr2 give up *int"<<endl;
cout<< *p <<endl;
delete p;
return 0;
}
unique_ptr 作为参数和返回值
unique_ptr 作为参数传递不会发生拷贝,但是会将对象所有权会转移到函数里,如下 ptr 会在 main 方法结束之前被销毁:
#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
void takeOwnership(std::unique_ptr<Resource> res)
{
if (res)
std::cout << *res << '\n';
} // the Resource is destroyed here
int main()
{
auto ptr{ std::make_unique<Resource>() };
// takeOwnership(ptr); // This doesn't work, need to use move semantics
takeOwnership(std::move(ptr)); // ok: use move semantics
std::cout << "Ending program\n";
return 0;
}
输出:
Resource acquired
I am a resource
Resource destroyed
Ending program
有时候不想对象的所有权转移到函数里,那么这时候可以通过 get 方法获取对象,如下:
#include <memory> // for std::unique_ptr
#include <iostream>
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource";
return out;
}
};
// The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<Resource>
void useResource(Resource* res)
{
if (res)
std::cout << *res << '\n';
else
std::cout << "No resource\n";
}
int main()
{
auto ptr{ std::make_unique<Resource>() };
useResource(ptr.get()); // note: get() used here to get a pointer to the Resource
std::cout << "Ending program\n";
return 0;
} // The Resource is destroyed here
输出:
Resource acquired
I am a resource
Ending program
Resource destroyed
unique_ptr 可以直接作为返回值返回:
#include <memory> // for std::unique_ptr
std::unique_ptr<Resource> createResource()
{
return std::make_unique<Resource>();
}
int main()
{
auto ptr{ createResource() };
// do whatever
return 0;
}
在 C++14 和之前的版本中会使用 move 语义来返回 unique_ptr 对象,在 C++17和之后的版本中,由于强制开启了返回值优化,所以 move 操作也不需要了,进行了RVO优化(具体可以看这里:https://en.wikipedia.org/wiki/Copy_elision),所以直接返回值比返回裸指针或引用更安全。
注意
不要让多个 unique_ptr 持有同一个对象,如下 res1 和 res2 将会试图释放多次 res:
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
std::unique_ptr<Resource> res2{ res };
用 unique_ptr 持有对象之后不要手动再去 delete 对象,如下会导致一个对象被释放多次:
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
delete res;
还需要注意异常问题,使用unique_ptr
并不能绝对地保证异常安全,如下:
some_function(std::unique_ptr<T>(new T), function_that_can_throw_exception());
C++ 标准并没有规定编译器对函数参数的求值次序,所以有可能出现这样的次序:
- 调用
new T
分配动态内存; - 调用
function_that_can_throw_exception()
函数; - 调用unique_ptr的构造函数;
假如调用 function_that_can_throw_exception 函数时抛出异常,而在这时 unique_ptr 还没构造,所以导致new T
所分配的内存不能回收,造成了内存泄露。解决这个问题,需要使用make_unique
函数:
some_function(std::make_unique<T>(), function_that_can_throw_exception());
因为对象 T 的创建和 unique_ptr 都是在 make_unique 函数里处理的,所以不会出现上面的顺序问题。
std::shared_ptr
shared_ptr 看这个名字就知道,和 unique_ptr 不同是它所管理的资源可以被多个对象持有。在底层实现中,shared_ptr 采用引用计数的方式实现,它里面有个内部控制器类型托管了一个计数器,这个内部控制器类型对象在shared_ptr第一次构造时以指针的形式保存在shared_ptr中。
shared_ptr重载了赋值运算符,在赋值和拷贝构造另一个shared_ptr时,这个指针被另一个shared_ptr共享。在引用计数归零时,这个内部类型指针与shared_ptr管理的资源一起被释放。
此外,为了保证线程安全性,引用计数器的加1,减1操作都是原子操作,它保证shared_ptr由多个线程共享时不会爆掉。
简单使用看下面这个例子:
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::shared_ptr
Resource* res { new Resource };
std::shared_ptr<Resource> ptr1{ res };
{
std::shared_ptr<Resource> ptr2 { ptr1 }; // make another std::shared_ptr pointing to the same thing
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed
输出:
Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed
在上面的例子中, ptr2 在自己的作用域中被创建,然后出了作用域后 ptr2 虽然被销毁,但是所管理的资源却在 main 方法结束后才被销毁。
下面再看一个例子,这也是很多同学的错误用法之一:
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
Resource* res { new Resource };
std::shared_ptr<Resource> ptr1 { res };
{
std::shared_ptr<Resource> ptr2 { res }; // create ptr2 directly from res (instead of ptr1)
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, and the allocated Resource is destroyed
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed again
输出:
Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed
上面这个例子会导致 crash,因为同一个资源被 delete 两次。我们注意到 ptr2 不是从 ptr1 copy 过来的,而是直接创建的,即使内部管理的资源是同一个,但是他们之间并没有关联,所以当 ptr2 走出作用域后会释放资源,ptr1 在 main 方法结束之后又会释放一次。
所以如果需要 shared_ptr 指向同一个资源,那么最佳实践是从已有的 shared_ptr copy 出来。
同样在 C++14 的时候新加入了 make_shared 函数,我们可以利用它构造一个 shared_ptr 对象:
#include <iostream>
#include <memory> // for std::shared_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::shared_ptr
auto ptr1 { std::make_shared<Resource>() };
{
auto ptr2 { ptr1 }; // create ptr2 using copy of ptr1
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Killing another shared pointer\n";
return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed
我们在 C++ 14 以后也要尽量使用 make_shared 来创建对象,它能保证在内存分配上能有更好的性能,也能函数参数的因编译器的初始化顺序而导致的问题(和上面 unique_ptr 问题一致)。
enable_shared_from_this
如果不小心直接在类里面返回 this 对象想要获得该对象的 shared_ptr,那么会让一个对象被 delete 两次,如下:
class Resource
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
std::shared_ptr<Resource> GetSPtr() {
return std::shared_ptr<Resource>(this);
}
};
int main()
{
auto sptr1 = std::make_shared<Resource>();
auto sptr2 = sptr1->GetSPtr();
return 0;
}
输出:
Resource acquired
Resource destroyed
double free or corruption (out)
上面的代码其实会生成两个独立的 shared_ptr,他们的控制块是独立的,所以导致 Resource 被释放了两次。
所以我们使用 enable_shared_from_this 可以避免上述情况:
class Resource : public std::enable_shared_from_this<Resource>
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
std::shared_ptr<Resource> GetSPtr() {
return shared_from_this();
}
};
注意
第一个是资源释放问题,使用 shared_ptr 的时候一定要注意是否有正确的处置它,因为可能某个 shared_ptr 没有释放,从而导致它所管理的资源没有释放。对于 unique_ptr 使用你只需要关心一个对象是否被释放,但是 shared_ptr 你需要关心所有对象是否被释放。比如下面的循环引用问题:
#include <iostream>
#include <memory> // for std::shared_ptr
#include <string>
class Person
{
std::string m_name;
std::shared_ptr<Person> m_partner; // initially created empty
public:
Person(const std::string &name): m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") }; // create a Person named "Lucy"
auto ricky { std::make_shared<Person>("Ricky") }; // create a Person named "Ricky"
partnerUp(lucy, ricky); // Make "Lucy" point to "Ricky" and vice-versa
return 0;
}
在上面我们创建了两个 Person Lucy 和 Ricky,然后让他们的内部 m_partner 相互赋值,结果就是他们两个对象形成了循环引用,都无法释放。
输出:
Lucy created
Ricky created
Lucy is now partnered with Ricky
第二是数组问题,在 C++17 以前 shared_ptr 是不能很好的托管数组对象的,所以不应该在 C++17 及以前使用 shared_ptr 托管 C 语言风格的数组。在 C++17 之前 shared_ptr 会通过 delete 而不是 delete[]
去删除被托管的数组。
所以我们有两种方式解决这种问题,第一种是使用 vector 代替 new[]
,如 shared_ptr<vector<int>>
;第二种是自定义 deleter,如:
template< typename T >
struct array_deleter
{
void operator ()( T const * p)
{
delete[] p;
}
};
创建 shared_ptr 应该这样写:
std::shared_ptr<int> sp(new int[10], array_deleter<int>());
具体可以看这里:https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used。
第三是我们可以通过 unique_ptr 转成 shared_ptr,但是 shared_ptr 是不能转成 unique_ptr的。例如:
std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
std::shared_ptr<std::string> shared = std::move(unique);
//或者
std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");
std::weak_ptr
在上面我们介绍了 shared_ptr 可能因为循环引用导致对象没有释放的问题,weak_ptr 就是设计用来解决这个问题。所以 weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它实际上不会托管对象,它指向一个由 shared_ptr 管理的对象而不影响所指对象的生命周期,也就是将一个 weak_ptr 绑定到一个shared_ptr不会改变 shared_ptr 的引用计数。
所以让我们看看上面的例子中如何用 weak_ptr 解决循环引用的问题:
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>
class Person
{
std::string m_name;
std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr
public:
Person(const std::string &name): m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") };
auto ricky { std::make_shared<Person>("Ricky") };
partnerUp(lucy, ricky);
return 0;
}
输出:
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky destroyed
Lucy destroyed
因为 Person 内部用的是 weak_ptr ,所以在上面的例子中,我们将 lucy 的 m_partner 指向了 Ricky,但由于这是个 weak_ptr ,所以并不会计数,那么在 main 方法结束之后都能够正常销毁。
因为 weak_ptr 并没有实现 ->
操作符,所以一般来说无法直接使用,所以我们可以通过 weak_ptr 的 lock 方法将 weak_ptr 转成 shared_ptr,如下 :
#include <iostream>
#include <memory> // for std::shared_ptr and std::weak_ptr
#include <string>
class Person
{
std::string m_name;
std::weak_ptr<Person> m_partner; // note: This is now a std::weak_ptr
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is now partnered with " << p2->m_name << '\n';
return true;
}
const std::shared_ptr<Person> getPartner() const { return m_partner.lock(); } // use lock() to convert weak_ptr to shared_ptr
const std::string& getName() const { return m_name; }
};
int main()
{
auto lucy { std::make_shared<Person>("Lucy") };
auto ricky { std::make_shared<Person>("Ricky") };
partnerUp(lucy, ricky);
auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';
return 0;
}
输出:
Lucy created
Ricky created
Lucy is now partnered with Ricky
Ricky's partner is: Lucy
Ricky destroyed
Lucy destroyed
当然被转换的 share_ptr 引用计数也会增加,我们可以验证一下:
int main()
{
auto lucy { std::make_shared<Person>("Lucy") };
auto ricky { std::make_shared<Person>("Ricky") };
partnerUp(lucy, ricky);
std::cout << lucy.use_count() << endl; //输出:1
auto partner = ricky->getPartner(); // get shared_ptr to Ricky's partner
std::cout << ricky->getName() << "'s partner is: " << partner->getName() << '\n';
std::cout << lucy.use_count() << endl; //输出:2
return 0;
}
weak_ptr 还可以避免悬挂指针
所谓悬挂指针(Dangling pointers)就是一个指针指向的对象被释放了,但是指针指向的目标并没有被修改,导致这个指针指向了一块被释放的内存,这样可能造成重复释放,还有其他不可预知的行为。
对于一个裸指针来说,我们是无法知道该指针指向的地址到底是否是一个悬挂指针,那么对于 weak_ptr 就可以通过 expired 方法来检测 weak_ptr 所指向的对象是否已经被释放,从而在一定程度上解决了这个问题。
我们先看一下例子:
// h/t to reader Waldo for an early version of this example
#include <iostream>
#include <memory>
class Resource
{
public:
Resource() { std::cerr << "Resource acquired\n"; }
~Resource() { std::cerr << "Resource destroyed\n"; }
};
// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
auto ptr{ std::make_shared<Resource>() };
return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed
// Returns a dumb pointer to an invalid object
Resource* getDumbPtr()
{
auto ptr{ std::make_unique<Resource>() };
return ptr.get();
} // ptr goes out of scope, Resource destroyed
int main()
{
auto dumb{ getDumbPtr() };//返回的裸指针
std::cout << "Our dumb ptr is: " << ((dumb == nullptr) ? "nullptr\n" : "non-null\n");
auto weak{ getWeakPtr() };//返回weak_ptr
std::cout << "Our weak ptr is: " << ((weak.expired()) ? "expired\n" : "valid\n");
return 0;
}
输出:
Resource acquired
Resource destroyed
Our dumb ptr is: non-null
Resource acquired
Resource destroyed
Our weak ptr is: expired
在上面的例子中 getDumbPtr 方法里面使用的是 unique_ptr 来托管对象,并且返回的是里面托管对象的指针,那么该对象会在 getDumbPtr 方法结束的时候被销毁,所以 getDumbPtr 返回的是一个悬挂指针,最后我们发现 dumb 仍然指向了一片地址,但是该地址的对象已经被释放了。
对于 getWeakPtr 方法来说返回的是 weak_ptr,但是我们上面说了 weak_ptr 不会托管对象,所以在 getWeakPtr 方法结束的时候,getWeakPtr 所返回的对象实际上已经被 share_ptr 销毁了,所以在 main 函数中 weak 实际上指向了一个失效的内存区域,不过我们通过调用 weak.expired()
返回 true 可以知道它指向的对象已经被销毁了。
weak_ptr 实现缓存
因为是单独的 expired 操作,所以可能会有并发问题,比如在 expired 之后对象被销毁了,那么再去使用的话可能产生无法预料的结果。所以对于这种判断,我们可以交给 lock 函数,如果 weak_ptr 没有被销毁,那么会返回 shared_ptr ,相当于把它的生命周期延长了,因为它递增了一个引用计数,如果 weak_ptr 已经被销毁了,那么会返回 nil。
// Returns a std::weak_ptr to an invalid object
std::weak_ptr<Resource> getWeakPtr()
{
auto ptr{ std::make_shared<Resource>() };
return std::weak_ptr{ ptr };
} // ptr goes out of scope, Resource destroyed
int main()
{
std::shared_ptr<Resource> spw = getWeakPtr().lock(); //return null
std::cout << "Our share ptr is: " << ((spw == nullptr) ? "nullptr\n" : "non-null\n");
std::shared_ptr<Resource> spw2(getWeakPtr()); //if object is expired,throw std::bad_weak_ptr
return 0;
}
输出:
Resource acquired
Resource destroyed
Our share ptr is: nullptr
Resource acquired
Resource destroyed
terminate called after throwing an instance of 'std::bad_weak_ptr'
what(): bad_weak_ptr
在上面的例子中 spw 会返回 null,spw2 会抛出异常。所以利用 lock 我们可以实现缓存操作,比如我们有个全局的 map 作为 cache,里面的 value 是 weak_ptr 类型的,那么每次在获取 cache 的时候就可以判断 lock 返回的值是否为空,不为空直接返回,为空重新加载缓存:
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
static std::unordered_map<WidgetID,std::weak_ptr<const Widget>> cache;
// objPtr is std::shared_ptr to cached object (or null
// if object's not in cache)
auto objPtr = cache[id].lock();
if (!objPtr) { // if not in cache,
objPtr = loadWidget(id); // load it
cache[id] = objPtr; // cache it
}
return objPtr;
}
总结
unique_ptr 它是一种独占资源所有权的指针,unique_ptr 会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的 Resource 对象,它只能使用 move 语义转移对象。所以如果你想操作一个指针,在进入作用域的时候分配好内存,然后在离开作用域的时候安全释放对象,那么可以使用它;
shared_ptr 它所管理的资源可以被多个对象持有,并且使用引用计数策略来释放对象,如果计数没有清零,那么它所管理的资源不会释放;
weak_ptr 它不管理对象,只是 shared_ptr 对象管理的资源的观察者,所以它不影响共享资源的生命周期,它一般使用在缓存、解决 shared_ptr 循环引用等地方。
Reference
https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one
https://stackoverflow.com/questions/4316727/returning-unique-ptr-from-functions
https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization
https://www.learncpp.com/cpp-tutorial/stdshared_ptr/
https://www.learncpp.com/cpp-tutorial/stdunique_ptr/
https://www.learncpp.com/cpp-tutorial/circular-dependency-issues-with-stdshared_ptr-and-stdweak_ptr/
https://stackoverflow.com/questions/13061979/shared-ptr-to-an-array-should-it-be-used
https://stackoverflow.com/questions/12030650/when-is-stdweak-ptr-useful
https://en.wikipedia.org/wiki/Dangling_pointer
https://stackoverflow.com/questions/16711697/is-there-any-use-for-unique-ptr-with-array
《Effective Modern C++》