从shared_from_this引发的一个错误看weak_ptr的应用
关于循环引用的概念一直了解,但理解并不深入。一直在使用shared_ptr,直到在项目中编写一个资源管理类时,指针在层级间流转造成运行时抛出std::bad_weak_ptr
。
在BS的《C++程序设计语言》中指出,应该尽量优先考虑使用std::unique_ptr
而非std::shared_ptr
。关于这一点,限于经验,在实际使用中总是很迷惑资源是否不会被共享。这个问题对我来说只能勤加思考,多多实践了。
关于循环引用的概念不多介绍,自行搜索即可。
问题
class CachePool;
class CConn final
{
public:
CConn(const std::shared_ptr<CPool>& pCPool)
: m_pCPool{pCPool}
{ }
~CConn() { }
private:
const std::shared_ptr<CPool> m_pCPool;
};
class CPool final
: std::enable_shared_from_this<CPool>
{
const int MAX_CACHE_CONN_CNT = 2;
public:
CPool();
~CPool();
int Init()
{
for (int i = 0; i < m_cur_conn_cnt; ++i){
auto pConn = std::make_shared<CConn>(shared_from_this());
m_free_list.push_back(pConn);
}
return 0;
}
private:
int m_cur_conn_cnt = MAX_CACHE_CONN_CNT;
std::list<std::shared_ptr<CConn>> m_free_list;
};
该代码在运行时抛出std::bad_weak_ptr
异常。
可以看到该代码,在管理类CPool
中拥有一个队列m_free_list
中存储着资源类CConn的智能指针列表,而在资源类中拥有一个指向管理类的std::shared_ptr
,用以访问管理类的接口,以提供对各个资源类都适用的信息。
以此形成循环引用。
解决方案
舍弃资源类中指向管理类的指针
可以将管理类中被资源类所需要的信息,在资源类的构造过程中传递给资源类,在资源类中以const &的形式进行存储与访问。
class CachePool;
class CConn final
{
public:
CacheConn(const int& cur_conn_cnt;)
: pool_cur_conn_cnt{cur_conn_cnt}
{ }
~CacheConn() { }
private:
const int& pool_cur_conn_cnt;
};
class CPool final
: std::enable_shared_from_this<CachePool>
{
const int MAX_CACHE_CONN_CNT = 2;
public:
CachePool();
~CachePool();
int Init()
{
for (int i = 0; i < m_cur_conn_cnt; ++i){
auto pConn = std::make_shared<CConn>(m_cur_conn_cnt);
m_free_list.push_back(pConn);
}
return 0;
}
private:
int m_cur_conn_cnt = MAX_C_CONN_CNT;
std::list<std::shared_ptr<CConn>> m_free_list;
};
这是一种限制性很大的解法。
其一,如果管理类中拥有的共享资源过多,导致资源类的参数列表过于繁冗。违反了接口简洁的设计原则。
其二,引用作为与指针类似概念的间接访问方式,拓展了资源的访问渠道,失去了std::shared_ptr
提供的线程安全保障:
也就是说,在多线程情况下,裸资源的线程安全性转移在程序员手中来维护,需要在每个资源类的使用中进行同步,增大了很多工作量,以及复杂的同步性问题中的出错概率和效率损失。(很难保证加锁粒度对性能的影响是否比share_ptr更低)。
使用weak_ptr打破循环引用
class CachePool;
class CConn final
{
public:
CConn(const std::weak_ptr<CPool>& pCPool)
: m_pCPool{pCPool}
{ }
~CConn() { }
private:
const std::weak_ptr<CPool> m_pCPool;
};
class CPool final
: std::enable_shared_from_this<CPool>
{
const int MAX_CACHE_CONN_CNT = 2;
public:
CachePool();
~CchePool();
int Init()
{
for (int i = 0; i < m_cur_conn_cnt; ++i){
auto pConn = std::make_shared<CConn>(weak_from_this());
m_free_list.push_back(pConn);
}
return 0;
}
private:
int m_cur_conn_cnt = MAX_CACHE_CONN_CNT;
std::list<std::shared_ptr<CConn>> m_free_list;
};
编译通过。