智能指针之与shared_ptr有关的enable_shared_from_this类模板04

一 enable_shared_from_this模板类详解

什么时候该使用enable_shared_from_this模板类?当我们需要一个类对象返回本身并且该类使用了shared_ptr智能指针时,就需要使用enable_shared_from_this。并且需要注意,当我们使用智能指针管理资源时,必须统一使用智能指针,而不能再某些地方使用智能指针,某些地方使用原始指针,否则不能保持智能指针的语义,从而产生各种错误。

1 错误使用this返回对象本身

错误代码如下:

class Test
{
public:
    //析构函数
    ~Test() { std::cout << "Test Destructor." << std::endl; }

    //获取指向当前对象的指针
    shared_ptr<Test> GetObject(){
        return shared_ptr<Test>(this);
    }
};

void test11(){
    {
        shared_ptr<Test> p(new Test());
        shared_ptr<Test> q = p->GetObject();
        cout<<endl;
    }
}

结果,原因是析构了两次new出来的Test:
在这里插入图片描述

为什么会这样呢?原因出在GetObject函数。它里面使用this给shared_ptr赋值,这就会导致原本shared_ptr p(new Test())时,该内存已经被p管理。当调用至shared_ptr(this),返回的匿名对象会被编译器再次用其创建新的匿名对象返回,然而,该匿名对象是由裸指针this赋值的,这就导致p与q各自引用相同的内存,但是引用计数都是1,因为p与q这两个shared_ptr是没有任何关系的。所以当p与q生命周期结束后,必定使同一片内存被释放两次导致报段错误。
实际上上面的错误就是下面例子。

int *p = new int(12);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);//必定报错,原因是两个没有任何关系的shared_ptr绑定同一片内存,导致释放两次。

2 改正this,使用enable_shared_from_this模板类返回对象本身

上面是错误的绑定this来获取shared_ptr即获取对象本身。那么有什么方法从一个类的成员函数中获取当前对象的shared_ptr呢,其实方法很简单:只需要该类继承至enable_shared_from_this模板类,然后调用该基类enable_shared_from_this中的shared_from_this即可。
正确代码:

class Test: public enable_shared_from_this<Test>//修改1
{
public:
    //析构函数
    ~Test() { std::cout << "Test Destructor." << std::endl; }

    //获取指向当前对象的指针
    shared_ptr<Test> GetObject(){
        //return shared_ptr<Test>(this);
        return shared_from_this();//修改2
    }
};

void test11(){
    {
        shared_ptr<Test> p(new Test());
        shared_ptr<Test> q = p->GetObject();
        cout<<endl;
    }
}

那为什么继承enable_shared_from_this后就可以正常使用呢?所以我们需要分析enable_shared_from_this这个类了,继续往下看。

3 分析enable_shared_from_this源码

为了方便理解,我们使用boost库的enable_shared_from_this类模板源码,实际上标准库的也差不多,标准库使用友元处理,而boost库使用public处理。
enable_shared_from_this.hpp文件,enable_shared_from_this模板类的实现如下:

template<class T> class enable_shared_from_this
{
protected:
    enable_shared_from_this() BOOST_NOEXCEPT
    {
    }
    enable_shared_from_this(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
    }
    enable_shared_from_this & operator=(enable_shared_from_this const &) BOOST_NOEXCEPT
    {
        return *this;
    }
    ~enable_shared_from_this() BOOST_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
    {
    }
public:
    shared_ptr<T> shared_from_this()
    {
        shared_ptr<T> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }
    shared_ptr<T const> shared_from_this() const
    {
        shared_ptr<T const> p( weak_this_ );
        BOOST_ASSERT( p.get() == this );
        return p;
    }
public: // actually private, but avoids compiler template friendship issues
    // Note: invoked automatically by shared_ptr; do not call
    template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    {
        if( weak_this_.expired() )
        {
            weak_this_ = shared_ptr<T>( *ppx, py );
        }
    }
private:
    mutable weak_ptr<T> weak_this_;
};

首先我们看到它有三个构造函数,而实际的public公有方法即能被调用的函数只有三个,其中_internal_accept_owner还被注明不能调用。所以该类非常简单,着重看不加const的shared_from_this(或者const的shared_from_this函数)外加私有成员weak_this_即可。
然后我们分析:

  • 1)如果想调用shared_from_this返回shared_ptr,那么就必须要初始化weak_this_ ,因为shared_ptr是由weak_this_赋值创建的,这里解释一下为什么使用若引用weak_ptr类型作为成员:因为弱引用不占引用计数,若我们使用shared_ptr强引用,那么该成员就必定占用一个计数,就会导致该内存本该由1->0释放,但是由于该成员的存在导致无法释放,造成内存延时释放,与逻辑不匹配,所以必须使用弱引用作为成员。
  • 2)接着上面讲,因为shared_ptr是由weak_this_赋值创建的,所以weak_this_必须被初始化,而整个类中只有_internal_accept_owner是初始化weak_this_的,该函数由shared_ptr构造时自动调用。所以目前逻辑就清楚了,只有shared_ptr构造被完整调用后,weak_this_才有值,然后shared_from_this才能被成功返回。但是注意,构造初始化完全的顺序刚好与这个顺序有点相反。例如:
shared_ptr<Test> p(new Test());

首先进入shared_ptr,但是会先去执行Test的构造,然后又因为enable_shared_from_this是Test的基类,所以最终先去执行完enable_shared_from_this的构造,再返回Test的构造执行完,最后返回shared_ptr的构造执行完。但是我们写代码时只需要记住必须只有shared_ptr被先执行,才能进入Test与enable_shared_from_this的构造。而不能越过shared_ptr的构造直接调用Test的构造和enable_shared_from_this的构造,这必然是错误的,因为没有shared_ptr的构造初始化weak_this_,shared_from_this返回的p肯定是非法的。

所以我们可以根据上面所说,列出常见继承于enable_shared_from_this类后,使用错误的代码。
错误1:

class Test : public enable_shared_from_this<Test>
{
    Test() { shared_ptr<Test> pTest = shared_from_this(); }
};

这种写法必然是错误的,上面已经说过,不能在构造shared_ptr完成之前调用shared_from_this,而enable_shared_from_this的构造和Test的构造必定在shared_ptr构造函数完成之前,所以shared_ptr的构造函数必定未执行完毕,也就是说weak_this_未被初始化,所以shared_from_this返回的是一个未知状态值。

有人就会说,那在pTest的构造函数之前创建一个shared_ptr不就行了吗,好,那我们就看错误2。
错误2:

Test() {
    shared_ptr<Test> sh(new Test());
    shared_ptr<Test> pTest = sh->shared_from_this();
	  //shared_ptr<Test> pTest = shared_from_this(); 
}

错误2这种情况实际上我真的不想列出来,当我们在Test构造中new一个Test本身,就会造成递归死循环调用该构造,根本就不会再往下执行,并且死循环过程中出现段错误,原因我猜可能是循环不断new,而没有释放内存,编译器认为出问题直接报错,因为我测试过不断new而不释放内存最终导致死机的情况。所以这种情况不再多说。

错误3:

class Test : public enable_shared_from_this<Test>
{
    void func() { shared_ptr<Test> pTest = shared_from_this(); }
};
int main()
{
    Test test;
    test.func();    //错误
    // Test *pTest = new Test;
    // pTest->func(); //同理错误
}

上面错误3的代码实际上错误1也是一样,都是没有执行完整shared_ptr的构造函数,导致weak_this_未被初始化。
gdb调试结果也报错weak_this_未被初始化。
在这里插入图片描述

正确的写法就好像我们第2大点的调用例子,必须先调用shared_ptr执行完整的shared_ptr构造函数初始化weak_this_ 后,才能调用shared_from_this。
正确写法:
注意:func是返回void,我们只是在函数体内测试是否能获取shared_ptr pTest = shared_from_this(),实际上我们获取本身直接将shared_from_this返回即可,当然返回值也需要换换。

class Test : public enable_shared_from_this<Test>
{
    void func() { shared_ptr<Test> pTest = shared_from_this(); }
};

int main()
{
    shared_ptr<Test> pTest( new Test() );
    pTest->func();
}

4 总结enable_shared_from_this的用法

  • 1)实际上enable_shared_from_this的用法非常简单,就是像第2大点那样继承,然后使用即可。只不过理解透需要点时间。
  • 2)获取本类对象本身,必须先调用shared_ptr p(new Test())执行完整个shared_ptr的构造函数,才能调用shared_from_this获取对象本身,而不能直接调用Test test这些自定义类对象的构造后,就调用shared_from_this,这是错误的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值