目录
问题:如何安全地将this指针返回给调用者
一般来说,我们不能直接将this指针返回。
想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变量并不知道,此时如果外部变量使用这个指针,就会使得程序崩溃。
使用智能指针shared_ptr看起来是个不错的解决方法。但问题是如何去使用它呢?我们来看如下代码:
struct Bad
{
std::shared_ptr<Bad> getptr() {
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
int main()
{
// Bad, each shared_ptr thinks it's the only owner of the object
std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
std::shared_ptr<Bad> bp2 = bp1->getptr();
std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
}// UB: double-delete of Bad
程序异常,输出:
bp2.use_count() = 1
Bad::~Bad() called
在 getptr函数构造智能指针时, 将裸指针this交给shared_ptr管理,shared_ptr无法确定这个对象是不是被 shared_ptr 管理着, 因此这样构造的 shared_ptr 并不是与其他 shared_ptr 共享一个计数器, 那么, 在析构时就会导致对象被重复释放, 从而引发错误。
如何在一个对象内部构造该对象的 shared_ptr , 即使该对象已经被 shared_ptr 管理着, 也不会造成对象被两个独立的智能指针管理。
正确做法是继承 enable_shared_from_this 类, 调用 shared_from_this() 函数生成 shared_ptr, 使用如下:
struct Good : std::enable_shared_from_this<Good> // note: public inheritance
{
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
};
struct Bad
{
std::shared_ptr<Bad> getptr() {
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
int main()
{
//Good: the two shared_ptr's share the same object
std::shared_ptr<Good> gp1 = std::make_shared<Good>();
std::shared_ptr<Good> gp2 = gp1->getptr();
std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';
// Bad: shared_from_this is called without having std::shared_ptr owning the caller
try {
Good not_so_good;
std::shared_ptr<Good> gp1 = not_so_good.getptr();
}
catch (std::bad_weak_ptr& e) {
// undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
std::cout << e.what() << '\n';
}
}
输出:
gp2.use_count() = 2
bad_weak_ptr
可以看到对象只够造了一次,使用shared_from_this只会把引用计数加1
enable_shared_from_this的实现分析
接着来看看enable_shared_from_this 是如何工作的,以下是它的源码:
// enable_shared_from_this的实现
// 基于(/usr/include/c++/7.3.0/bits/shared_ptr.h)
// 此代码是对gcc实现的简化版本, 仅作为描述原理用.
template<typename T>
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this()
{
return shared_ptr<T>(this->weak_this);
}
shared_ptr<const T> shared_from_this() const
{
return shared_ptr<const T>(this->weak_this);
}
private:
template<typename>
friend class shared_ptr;
template<typename T1>
void _M_weak_assign(T1* p, const shared_count<>& n)
{
weak_this._M_assign(p, n);
}
mutable weak_ptr<T> weak_this;
};
enable_shared_from_this<T> 类中定义了一个 weak_ptr<T>, 起到了上文提到的从obj指针生成 shared_ptr<T> 对象的作用. 按照先前的原理, 我们可能认为是在obj初始化的时候, 同时对 weak_this 进行初始化, 但是在这段代码里显然没有对 weak_this 进行任何初始化工作(原始代码里也没有,为什么? 这是因为当对象没有由智能指针管理时, 这些操作是没有必要的. 所以应该把这个任务交给 shared_ptr).
在 shared_ptr<T> 的构造函数中对 weak_ptr<T> 进行处理. 从 Good 类来看, 就是在 std::make_shared<Good>();处对 Good 对象中的 weak_ptr<Good> weak_this 进行处理, 使其指向一个有效的 Good 对象, 并修改 use_count. 上面 Good 类对 enable_shared_from_this 的使用是少数几种有效的方法, 必须保证, 如果对一个对象调用 shared_from_this(), 该对象必须是由 shared_ptr<T> 持有的. 从上一段的原理中可以理解这样做的原因: 第一个持有 Good 对象 g_obj 的 shared_ptr<T> sp1 会对 g_obj 的 weak_this 进行处理, 使其有效. 如果没有这一步, 在调用 shared_from_this() 时, weak_this 是一个无效值, 即 weak_this.expire() == true, 就会抛出异常.
那么在 shared_ptr 的构造函数中是如何处理 weak_ptr 的呢?在 shared_ptr 中定义了这样一个函数
template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept
{
if(auto __base = __enable_shared_from_this_base(_M_refcount, __p))
__base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}
template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<!__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp*) noexcept { }
其中 _Yp 是 shared_ptr 管理的对象的类型. 这两个模板函数表示:
当 _Yp 是 enable_shared_from_this 的子类时, 就会生成第一个函数, 其功能是通过 _Yp 对象的指针来调用其 _M_weak_assign 函数以修改 _Yp 对象的 weak_this 成员, 而实际上 _M_weak_assign 调用的是 _M_assign 函数.
否则生成第二个函数体为空的函数.
// from shared_ptr_base.h class __weak_ptr, derived by weak_ptr
void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
{
if (use_count() == 0)
{
_M_ptr = __ptr;
_M_refcount = __refcount;
}
}
_M_enable_shared_from_this_with 函数在 shared_ptr<_Yp> 的构造函数中被调用, 从而检测 _Yp 是否继承自 make_shared_from_this, 并进行相应的处理. 这里的 _M_refcount 是 shared_ptr 的成员, 用来记录 _Yp 被多少 shared_ptr 管理. 这样, 就完成了对 weak_ptr 的处理, 使其成为一个有效值. 在以后调用 shared_from_this() 函数时, 就能利用 weak_this 调用 shared_ptr 的构造函数, 从而生成一个共享同一对象的 shared_ptr.