C++ 智能指针 -- 看 Envoy 如何用 weak_ptr
潘忠显 / 2020-08-13
weak_ptr
的基本用法
weak_ptr
的作用表达临时所有权,即:某个对象只有存在时才需要被访问,而且随时可能被删除。
让我们先看一下 weak_ptr
的构造方式:
constexpr weak_ptr() noexcept;
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
=
操作符与复制构造类似。
从构造函数可以看出,** weak_ptr
只能通过 weak_ptr
或 shared_ptr
进行构造**。
如果要使用 weak_ptr
指向的信息块的管理的对象指针,需要使用 lock()
函数先转成 shared_ptr
。如果管理的对象已经被释放了,lock会返回一个空指针:
std::shared_ptr<T> lock() const noexcept;
除了 lock()
函数之外,还可以使用以下两个函数:
use_count()
,返回shared_ptr指向该实例的个数expired()
,等价于use_count() == 0
,即没有shared_ptr指针指向该实例
在 shared_ptr
一文中有提到多线程下 use_count()
只是一个大约值:
In multithreaded environment, the value returned by use_count is approximate (typical implementations use a memory_order_relaxed load) 多线程环境下,
use_count
返回的值是近似的(典型实现使用 memory_order_relaxed 加载)
但 weak_ptr
的说明中没有提这一点,实际上是不是准确值呢?
Envoy中的使用 weak_ptr
的例子
Envoy 的 SingletonManagerImpl
的成员函数 get()
中有这样一段代码:
// using InstanceSharedPtr = std::shared_ptr<Instance>;
// std::string name;
// SingletonFactoryCb cb;
// std::unordered_map<std::string, std::weak_ptr<Instance>> singletons_;
if (nullptr == singletons_[name].lock()) {
InstanceSharedPtr singleton = cb();
singletons_[name] = singleton;
return singleton;
} else {
return singletons_[name].lock();
}
上述代码使用一个unordered_map存储了 单例名字--> 单例的weak_ptr
的映射关系。
使用过程:
- 如果没有对应名的实例,则使用
shared_ptr
创建一个对象并返回 - 否则,返回shared_ptr
作用:
如果产生了单例之后,如果大家的 shared_ptr
都释放了,则会删掉单例,下次再使用的时候,会检查指针是否有指向未被删除的对象,如果没有则需要创建。
有没有发现什么问题?
这里有一个明显的线程安全问题:
- 两个线程同时请求时,可能同时调用lock()函数返回为空
- 连个线程都去创建实例,并先后赋值到给singletons_[name]
- 函数退出,只有一个线程中创建的那个是真正写到管理器的
这样全局就同时存在两个该工厂产生的实例。
不过,Envoy的代码中也有注释描述了这个问题:
It is assumed the singleton manager is only used on the main thread so it is not thread safe. Asserts verify that.
如何保证只能在主线程中使用?
使用 ASSERT
,在get方法中加入了判断,只能是创建SingletonManagerImpl的线程才能使用这一单例管理器:
// run_tid_(thread_factory.currentThreadId())
assert(run_tid_ == thread_factory_.currentThreadId());