《白话C++》第10章 STL和boost,Page105 enable_shared_from_this

说到“循环引用”,其中“自己对自己”的引用是最直接的循环引用,如图10-12所示。

而说到“自己”,在C++语言中应该首先想到的类的“this”指针。不过,this指针是裸指针,如果我们在类中,需要传递当前对象本身,可是接受方却只能接受本类的智能指针,那该怎么办呢?比如前面提到的那个函数:

Coo::Ptr foo(int a, Coo::Ptr pcoo)
{
    ...
}

foo函数有个入参必须是shared_ptr <Coo> 类型,假设在Coo中需要调用该函数:

class Coo
{
public:
    typedef std::shared_ptr <Coo> Ptr;

    void Test()
    {
        foo(123, ______);
        ...
    }
...
};

下划线处,该填写什么呢?填this? foo不接受,编译失败。

既然this是裸指针,那就从它身上创建一个shared_ptr呗:

void Test()
{
    shared_ptr <Coo> shared_this(this);
    foo(123, shared_this);
    ...
}

编译,成功!解决问题易如反掌啊,可惜乐得太早了,有大问题发生了。

先看看,如果类中this所代表的当前Coo对象,是这么创建的:

//灾难一:
int main()
{
    Coo coo_1; //coo_1是个栈对象
    coo_1.Test();
}

 使用coo_1调用成员函数Test()时,Test()中的this就是coo_1。coo_1是一个栈对象它会在main()函数结束时自动释放。然而,foo()函数接过去的那个shared_ptr对象,它也会尝试释放所拥有的裸指针,也就是this,而  *this就是coo_1,而coo_1……它是一个会自动释放的栈对象!一个对象会被释放两次就是灾难,如果这个对象还是栈对象,那就是史诗般的灾难了。大家可以试试,

要不小心点,使用堆对象吧:

//灾难二:
int main()
{
    Coo* coo_2 = new Coo; //coo_2是个堆对象
    coo_2->Test();
    //coo_2->Test();
}

现在的问题是,main()函数中要不要主动delete coo_2如果释放,就会发生重复释放的问题,如果不释放,有谁能从这几行代码中看出,当coo_2第一次调用了Test()之后,coo_2就很可能“挂掉了”呢?如果有人又调用Test()一次,他知道其实coo_2是一个尸体吗?

还好《白话C++》教会我们,如果一个类的对象需要用到shared_ptr,那就在一开始绑定shared_ptr,所以,第三个版本的灾难片呼啸而来:

//灾难三:
int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

因为绑定了shared_ptr,所以背地里所创建的Coo堆对象肯定会被释放啦,然而一运行程序,世界还是崩溃了。

问:出现过几个shared_ptr在共同管理背后所创建的Coo堆对象?

答:两个,一个是coo_3,另一个是Test()成员函数中的shared_this

再问:那么,shared_this是从前一个智能指针创建出来的吗?

答:不是,它是从裸指针this创建出来的。

只要是从裸指针创建出来的,无论是来自this,还是来自某个智能指针的get()操作,都会让新建的智能指针以为自己是这个裸指针的第一个保护者……

真相大白,罪魁祸首正是this这个裸指针。每次我们用this创建一个新的shared_ptr对裸指针的计数管理都会从新从1开始

this在关键时刻掉了链子,造成一堆管理同一对象却互不引用的shared_ptr

自己的理解:  也就是说,出了main函数后,第4行的coo_3会释放,同时释放掉coo_3守护的裸指针this。然而Test()函数中用到shared_this,它认为,自己是裸指针this的第一个守护者,所以,它也会将this再释放一遍。造成灾难。

手工解决的话,直觉上可能会为该类添加一个“shared_ptr”版本的this成员数据:

class Coo
{
    ...
private:
    shared_ptr <Coo> _shared_this;
};

但应该立刻清醒过来,这是在“自己引用自己”!所以应该换成weak_ptr:

​class Coo
{
    ...
private:
    weak_ptr <Coo> _weak_this;
};

怎么初始化这个成员呢?干脆提供一个接口:

class Coo
{
    ...
public:
    void SetWeakThis(shared_ptr <Coo> shared_this)
    {
        _weak_this = shared_this;
    }
private:
    weak_ptr <Coo> _weak_this;
};

可想而知,每次创建C的对象(智能指针)时,都得马上调用SetWeakThis():

...
shared_ptr <Coo> sp = make_shared <Coo> ();
sp->SetWeakThis(sp);

这样的代码又麻烦又丑陋。还好,标准库为我们提供了一个工具类,它有一个很长但直观的名字,叫“enable_shared_from this(允许从自身共享)”,严格上讲是一个类模板。使用方法是将它作为基类,并将当前类作为它的模板入参。

很明显,派生它是希望继承它的名字所表达的能力,所以很适合采用私用派生:

class Coo : std::enabled_shared_from_this <Coo>
{
public:
    void Test()
    {
        foo(123, shared_from_this());
        ...
    }
};

注意尖括号中的Coo。再注意加粗的“shared_from_this()”,这就是我们继承所得的能力,它保障传递给foo一个正确的shared_ptr<Coo>。测试一下:

//正确:
int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

一切都很正确,不存在重复释放。

让Coo认enable_shared_from_this<Coo>为父,更是注定Coo必须以智能指针面试,否则程序在运行时将因调用“shared_from_this()”而出错,原因可参考之前的分析,现在先牢记结论:

//运行:程序崩溃

int main()
{
    Coo coo_1; 
    coo_1.Test();
}

//运行:程序崩溃

int main()
{

    //普通堆对象
    Coo* coo_2 = new Coo; 
    coo_2->Test();
}

//运行正常

int main()
{
    Coo::Ptr coo_3 = make_shared <Coo> ();
    coo_3->Test();
}

【课堂作业】:指定要求的二叉树定义

请使用shared_ptr结合enable_shared_from_this等技术,实现一颗“二叉树”结构的定义。二叉树由节点组成。要求每个节点可以拥有一左一右的两个子节点,或者不拥有子节点(称为叶子节点),节点还需要拥有一个指针,指向父节点,最顶层的节点称为“跟节点”,它的父节点为空。要求提供从根节点开始,逐步添加左右节点以生成一棵树的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值