【问题标题】:Returning a weak_ptr member variable返回一个weak_ptr成员变量
【发布时间】:2020-08-26 14:38:48
【问题描述】:

我有以下TreeNode 类,它可以存储对其父节点的引用以及指向其所有子节点的指针向量。

树节点

class TreeNode : public std::enable_shared_from_this<TreeNode>
{
private:
    int val_;
    std::weak_ptr<TreeNode> parent_;
    std::vector<std::shared_ptr<TreeNode>> children_;
public:

    TreeNode(int val) : val_(val) {}

    void addChild(std::shared_ptr<TreeNode> child)
    {
        child->parent_ = shared_from_this();
        children_.push_back(std::move(child));
    }

    const int& getVal() const
    {
        return val_;
    }

    std::weak_ptr<TreeNode> getParent()
    {
        return parent_;
    }
};

parent_ 存储为weak_ptr,这样父子之间就没有循环依赖/内存泄漏。

主要

int main(int argc, char** argv) {
    std::shared_ptr<TreeNode> parent = std::make_shared<TreeNode>(1);
    std::shared_ptr<TreeNode> child = std::make_shared<TreeNode>(2);

    parent->addChild(child);

    // child->getParent()->getVal(); not possible
    return 0;
}

使用此类时,我想访问任何节点的父节点的值(...否则为什么要存储父节点),但由于 parent_ 是 weak_ptr,我无法调用任何成员函数它。

我知道这方面存在一些“黑客行为”,例如使用lock 从weak_ptr 中提取shared_ptr,但我觉得这会使API 变得笨拙。我想知道返回 weak_ptr 的推荐 C++11 方法是什么,它仍然可以使用。

编辑:

我可能不应该将 lock 称为“黑客”,而是从 API 的角度来看不够优雅。

例如,如果我想访问parent_,我必须这样做

主要

if (!child->getParent()->expired())
{
    std::cout << child->getParent()->lock()->getVal() << std::endl;
}

这似乎相当冗长,所以我想知道是否有任何语言功能可以利用来避免做所有这些。

【问题讨论】:

  • 这不是黑客行为。为了使用 weak_ptr 指向的内容,您必须确认指向的对象 sill 存在,并确保它在您使用时继续存在。这就是lock 的用途。
  • 是什么让您认为lock 是黑客? weak_ptr 没有它就无法使用
  • 对不起,我不应该使用“hack”这个词,我已经编辑了原始帖子以说明为什么这可能会导致冗长的 API。
  • 与你的橡皮鸭讨论如果在if (!child-&gt;getParent()-&gt;expired()) 确认对象仍然存在后立即销毁对象会发生什么。
  • 时髦。你明白发生了什么。很高兴我没有尝试正式回答这个问题。据我所知,没有内置的帮助程序,这将违反 C++ 的规定,您只需为您要求的政策付费,以默默地暗示锁定。我承认这该死的有用,但你不必承认你正在支付转换税。但既然你知道打电话给getParent 的人会和那个父母做点什么,我会返回一个shared_ptr 并将lock 隐藏在getParent 中。

标签: c++ c++11 shared-ptr smart-pointers


【解决方案1】:

没有办法在weak_ptr 上调用lock,weak_ptr 的全部意义在于它不计入它所指向的对象的引用计数,同时仍然允许使用控件阻止检查对象是否有效,然后获取shared_ptr。

对我来说,parent_ 不应该是 weak_ptr,而是原始指针。使用原始指针不是坏习惯或“不现代”,它们只是不应该拥有任何内存。有了它,您可以检查它是否是nullptr,以检查节点是否有父节点。

您在weak_ptr 上使用lock 的示例也是错误的。 shared_ptr 和 weak_ptr 被许多线程同时创建和销毁,这就是它们的大部分用例所在。

if (!child->getParent()->expired())
{
    std::cout << child->getParent()->lock()->getVal() << std::endl;
}

在您的示例中,您首先使用expired 成员函数检查指针是否仍然有效,如果是,则使用lock 获取将有助于对象引用计数的shared_ptr。如果有多个线程可能正在使用该指针,则在您的expired 调用之后和lock 调用之前,您的引用计数可能会达到0,并且可以从内存中删除对象。然后,您将从lock 获得一个nullptr shared_ptr,并在取消引用它时获得未定义的行为。

如果您继续使用weak_ptr,请这样做。

if(auto parent_ptr = child->getParent()->lock())
{
   // parent_ptr is shared_ptr that is not null here
}

【讨论】:

  • 谢谢,我认为没有理由反对在我的案例中使用原始指针。另外,你有什么理由用auto 写if 声明吗?
  • 这样你就可以检查它是否是nullptr,以检查一个节点是否有一个父节点。如果有一个父节点,这里可能会搞砸从图中删除。破坏父级必须调用所有子级并将其父指针设为空。并且任何对返回的原始指针进行操作的人都不知道该对象已经消失。如果您要使用shared_ptr,请正确使用它并获得所有保证。当您不这样做时,有人会假设他们是安全的而编写代码并因为他们不安全而受到严重的冲击。
  • @Carpetfizz ,无论你选择什么路径,全力以赴。不要半途而废。它永远不会起作用。
  • @user4581301 谢谢,我没有考虑到我必须手动将子项的所有父引用设为空。仅出于这个原因,我宁愿使用weak_ptr 并进行必要的检查。
猜你喜欢
  • 1970-01-01
  • 2019-07-28
  • 2016-02-09
  • 1970-01-01
  • 2023-03-03
  • 2018-05-25
  • 2011-11-12
  • 1970-01-01
  • 2021-01-22
相关资源
最近更新 更多