Item 20: Use std::weak_ptr for std::shared_ptr like pointers that can dangle.
std::weak_ptr 的特点
std::weak_ptr 通常不会单独使用,一般是与 std::shared_ptr 搭配使用,可以将 std::weak_ptr 类型指针视为 std::shared_ptr 指针的一种辅助工具,借用 std::weak_ptr 类型指针, 可以获取 std::shared_ptr 指针的一些状态信息,例如有多少 std::shared_ptr 指针指向相同的资源、std::shared_ptr 指针指向的内存是否已经被释放等。
std::weak_ptr 常常是通过 std::shared_ptr 构造而来,它和 std::shard_ptr 指向的相同的位置。但是,std::weak_ptr 不会影响对象的引用计数,也就是说,std::weak_ptr 被创建时,引用计数不会增加,当它被释放时,引用计数也不会减少。
auto spw =
std::make_shared<Widget>();
…
std::weak_ptr<Widget> wpw(spw);
…
spw = nullptr;
if (wpw.expired()) …
std::weak_ptr 没有解引用操作,但可以将它转换为 std::shared_ptr,使用 lock 可以保证线程安全。
std::shared_ptr<Widget> spw1 = wpw.lock();
auto spw2 = wpw.lock();
std::shared_ptr<Widget> spw3(wpw);
std::weak_ptr 的典型应用
下面介绍 std::weak_ptr 的两个典型应用,其实本质上就是利用了 std::weak_ptr 的特点:共享资源所有权,但又不增加其引用计数。
循环引用
std::weak_ptr 的一个典型应用是解决 std::shared_ptr 的内存泄露问题----循环引用。看下面的代码:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
shared_ptr<B> b;
};
class B {
public:
B() { cout << "B constructor" << endl; }
~B() { cout << "B's destructor" << endl; }
shared_ptr<A> a;
};
int main() {
std::shared_ptr<A> aa = make_shared<A>();
std::shared_ptr<B> bb = make_shared<B>();
aa->b = bb;
bb->a = aa;
return 0;
}
A constructor
B constructor
从运行结果可以看到 A 和 B 都调用了构造函数,却没有调用析构函数,导致了资源泄露。原因是 main 函数结束后,两个对象的引用计数都为 1 ,导致 std::shared_ptr 没有调用析构函数。解决办法是将 A 和 B 对象中 shared_ptr 换成 weak_ptr 即可。
带缓存的工厂方法
当调用工厂方法的代价比较高时,可以通过增加缓存来优化。但是把所有对象都缓存下来会造成效率问题,当对象不再使用时,可以销毁其缓存。
示例代码参考这里
#include <iostream>
#include <unordered_map>
#include <memory>
using namespace std;
class Investment
{
public:
virtual ~Investment() = default;
public:
virtual void doWork() = 0;
};
class Stock : public Investment
{
public:
~Stock() {
cout << "~Stock() called....\n";
}
public:
virtual void doWork() override {
cout << "Stock doWork....\n";
}
};
class Bond : public Investment
{
public:
~Bond() {
cout << "~Bond() called....\n";
}
public:
virtual void doWork() override {
cout << "Bond doWork....\n";
}
};
enum class InvestType {
INVEST_TYPE_STOCK,
INVEST_TYPE_BOND,
};
auto makeInvestment(InvestType type)
{
auto delInvmt = [](Investment *pInvestment) {
cout << "custom delInvmt called...." << pInvestment << endl;
if (pInvestment)
{
delete pInvestment;
}
};
shared_ptr<Investment> pInv(nullptr);
switch (type)
{
case InvestType::INVEST_TYPE_STOCK:
pInv.reset(new Stock, delInvmt);
break;
case InvestType::INVEST_TYPE_BOND:
pInv.reset(new Bond);
break;
}
return pInv;
}
shared_ptr<Investment> fastLoadInvestment(InvestType type)
{
static unordered_map<InvestType, weak_ptr<Investment>> s_cache;
auto pInv = s_cache[type].lock();
if (!pInv)
{
cout << "create new investment..\n";
pInv = makeInvestment(type);
s_cache[type] = pInv;
}
return pInv;
}
int main () {
{
auto pInv = fastLoadInvestment(InvestType::INVEST_TYPE_BOND);
pInv->doWork();
}
cout << "-------------------------------\n";
{
auto pInv = fastLoadInvestment(InvestType::INVEST_TYPE_BOND);
pInv->doWork();
}
cout << "-------------------------------\n";
{
auto pInv = fastLoadInvestment(InvestType::INVEST_TYPE_STOCK);
pInv->doWork();
auto pInv2 = fastLoadInvestment(InvestType::INVEST_TYPE_STOCK);
pInv2->doWork();
}
return 0;
}
create new investment..
Bond doWork....
~Bond() called....
-------------------------------
create new investment..
Bond doWork....
~Bond() called....
-------------------------------
create new investment..
Stock doWork....
Stock doWork....
custom delInvmt called....0x1258cd0
~Stock() called....
对象的缓存管理器需要一个类似 std::shared_ptr 的指针,但又想这些对象的生存期可以由调用者来管理来管理,因而使用 std::weak_ptr 可以满足这种需求。
|