std::shared_ptr of this

144

我目前正在学习如何使用智能指针。然而在进行一些实验时,我发现了以下情况,但我找不到一个令人满意的解决方案:

假设你有一个A类对象作为一个B类对象(子对象)的父对象,但两者都应该相互知道:

class A;
class B;

class A
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children->push_back(child);

        // How to do pass the pointer correctly?
        // child->setParent(this);  // wrong
        //                  ^^^^
    }

private:        
    std::list<std::shared_ptr<B>> children;
};

class B
{
public:
    setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    };

private:
    std::shared_ptr<A> parent;
};

这个问题是关于一个A类对象如何传递其自身(this)的 std::shared_ptr 给它的子类。

有针对 Boost shared pointers 的解决方案(获取 boost::shared_ptr 以供使用),但如何使用 std:: 智能指针来处理呢?


2
与任何其他工具一样,您必须在适当的时候使用它。对于您正在做的事情使用智能指针不是一个好选择。 - YePhIcK
类似于boost。请参见此处 - juanchopanza
1
这是一个抽象层面的问题。你甚至不知道“this”指向堆上的内存。 - Vaughn Cato
语言本身并不会,但是你会。只要你记住每个东西在哪里,就没问题了。 - Alex
2个回答

215

为此目的,有一个std::enable_shared_from_this。继承它后,您可以从类内部调用.shared_from_this()。此外,您正在创建可能导致资源泄漏的循环依赖项。可以使用std::weak_ptr来解决这个问题。因此,您的代码可能如下所示(假设子级依赖于父级的存在而不是相反):

class A;
class B;

class A
    : public std::enable_shared_from_this<A>
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children.push_back(child);

        // like this
        child->setParent(shared_from_this());  // ok
        //               ^^^^^^^^^^^^^^^^^^
    }

private:     
    // note weak_ptr   
    std::list<std::weak_ptr<B>> children;
    //             ^^^^^^^^
};

class B
{
public:
    void setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    }

private:
    std::shared_ptr<A> parent;
};

需要注意的是,调用 .shared_from_this() 要求在调用时 this 必须由 std::shared_ptr 持有。这意味着您无法再在堆栈上创建此类对象,并且通常不能在构造函数或析构函数内调用 .shared_from_this()


2
感谢您的解释,并指出我的循环依赖问题。 - Icarus
1
@Deduplicator,那是一个毫无意义的共享指针,原谅我的俏皮话。该构造函数旨在与托管对象或其基类的成员指针一起使用。无论如何,你的观点是什么(我很抱歉)?这些非所有权的shared_ptr对此问题没有影响。 shared_from_this的前提条件明确指出,在调用时,对象必须由某个shared_ptr拥有(而不仅仅是指向)。 - yuri kilochek
1
考虑到父对象通常应该拥有子对象,如果在父类中声明一个指向子对象的weak_ptr,而子对象持有父对象的所有权(shared_ptr),这不会让人感到困惑吗?那么现在谁拥有子对象呢? - pengMiao
1
当对子节点的引用仅为弱指针,且仅直接持有根节点时,是什么保持了树的完整性? - Jimmy R.T.
1
对于回答OP问题的响应,我放弃了计数。那些不回答问题而只是建议“更好的方法”的人经常是错误的。我也进行了计数,因为我需要这个问题的答案。我多次使用的习语是将“this”放入容器中以获取所有类实例的完整列表,即遍历容器以处理每个实例。请不要费力解释为什么这会有问题。我知道问题以及如何避免它们。 - rm1948
显示剩余4条评论

13

您的设计存在几个问题,这些问题似乎源于您对智能指针的误解。

智能指针用于声明所有权。您通过声明每个父对象都拥有所有子对象,但同时也声明每个子对象都拥有其父对象来破坏了这一点。这两种情况都不能成立。

此外,在 getChild() 中返回弱指针。这样做会声明调用者不需要关心所有权。现在这可能非常有限,但是这样做也必须确保所涉及的子对象在任何弱指针仍然被持有时不会被销毁。如果使用智能指针,则可以自动解决该问题。

最后一件事。通常,在接受新实体时,您应该接受原始指针。智能指针可以有其自己的意义,用于在父对象之间交换子对象,但对于一般用途,您应该接受原始指针。


你好,我知道这已经过时了,抱歉。你说孩子不应该拥有他们的父母(而父母却拥有孩子),因为显然这是一个循环依赖。但是,如果一个子项需要知道它的父项呢?它应该存储一个弱指针指向父项吗?原始指针?或者这个想法本身就有问题?我问的原因是因为Qt QAbstractItemModel要求子项知道它是父项的哪个索引的子项。这最终导致了child->parent->children.indexOf(child)类型的调用,可能是糟糕的设计,但它就是这样! - Dan
1
@Dan 根据当地的风格指南,我通常使用原始指针来表示非拥有引用。我们也可能在某个时候得到一些标准:https://en.cppreference.com/w/cpp/experimental/observer_ptr - Šimon Tóth
1
非常感谢!那就是最终我选择的(裸指针)。了解observer_ptr很好 - 我不断对现代C++的发展方向印象深刻。 - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接