一、特性
std::weak_ptr
并不是一种独立的智能指针,而是std::shared_ptr
的一种扩充。
std::weak_ptr
一般是由std::shared_ptr
创建的,之后两者就指涉到相同的控制块,但std::weak_ptr
并不会影响所指涉对象的引用计数。
二、使用场景
1. 带缓存功能的工厂函数
为什么要加缓存
当调用工厂函数成本高昂(以为其执行了文件或数据库的I/O操作),且ID会被频繁的重复使用的话,则就需要加一级缓存来优化。
为什么要用std::weak_ptr
首先,由调用者去决定这些对象(工厂函数返回的对象)的生存期(std::shared_ptr
做不到),然而函数内存的缓存管理器也需要一个指涉到这些对象的指针(std::unique_ptr
做不到),并且缓存管理器的指针需要能够校验它们何时会空悬(裸指针
做不到)。
示例:
#ifndef WEAKPTRDEMO_H
#define WEAKPTRDEMO_H
#include <iostream>
#include <unordered_map>
#include <memory>
namespace T18_NS {
using namespace std;
/*!
* \brief The Investment class 基类
*/
class Investment
{
public:
virtual ~Investment() = default;
public:
virtual void doWork() = 0;
};
/*!
* \brief The Stock class 派生类
*/
class Stock : public Investment
{
public:
~Stock() {
cout << "~Stock() called....\n";
}
public:
virtual void doWork() override {
cout << "Stock doWork....\n";
}
};
/*!
* \brief The Stock class 派生类
*/
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)
{
// 自定义析构器, 这里以lambda表达式的形式给出
auto delInvmt = [](Investment *pInvestment) {
cout << "custom delInvmt called...." << pInvestment << endl;
// 注意:pInvestment可能为空指针,比如默认为空,然后调用reset赋值时,会先调用一遍析构
if (pInvestment)
{
// TODO 自定义析构时想干的事
delete pInvestment;
}
};
// 待返回的指针, 初始化为空指针
shared_ptr<Investment> pInv(nullptr);
// 注意这里用reset来指定pInv获取从new产生的对象的所有权, 不能用=赋值
switch (type)
{
case InvestType::INVEST_TYPE_STOCK:
// 注意:自定义析构器是随对象一起指定的,这里区别于unique_ptr
pInv.reset(new Stock, delInvmt);
break;
case InvestType::INVEST_TYPE_BOND:
// 如果不指定自定义析构器的话,则不会调用
pInv.reset(new Bond);
break;
}
// 返回智能指针
return pInv;
}
// 带缓存的工厂函数
// 使用场景:当调用工厂函数makeInvestment成本高昂(e.g. 会执行一些文件或数据块的I/O操作), 并且type会频繁的重复调用
shared_ptr<Investment> fastLoadInvestment(InvestType type)
{
// 定义一个做缓存的容器,注意这里存的内容是weak_ptr
// 使用weak_ptr的好处是,它不会影响所指涉对象的引用计数
// 如果这里改为shared_ptr的话,则函数外边永远不会析构掉这个对象, 因为缓存中至少保证其引用计数为1。这就背离的我们的设计
static unordered_map<InvestType, weak_ptr<Investment>> s_cache;
// 将weak_ptr生成shared_ptr
auto pInv = s_cache[type].lock();
// 如果缓存中没有的话,则调用工厂函数创建一个新对象,并且加入到缓存中去
if (!pInv)
{
cout << "create new investment..\n";
pInv = makeInvestment(type);
s_cache[type] = pInv;
}
return pInv;
}
void test()
{
{
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();
}
}
} // T18_NS
#endif // WEAKPTRDEMO_H
2. 观察者设计模式
该模式的主要组件是主题和观察者,在多数实现中,每个主题包含一个数据成员,该成员持有指涉到观察者的指针,这才能使得主题能够很容易的在其发生状态改变时发出通知。主题不会控制其观察者的生存期(即不关心它们何时析构),但是需要确认当一个观察者被析构之后,主题不会去访问它。一个合理的设计就是让每个主题只有一个容器来放置指涉到其观察者的std::weak_ptr
,以便主题在使用某个指针之前,能够先确定它是否空悬。