【问题标题】:How to prevent double object deletion using smart pointers?如何使用智能指针防止双重对象删除?
【发布时间】:2023-03-04 21:28:01
【问题描述】:

我有一个类拥有它的孩子的所有权:

class Child
{
public:
    Child() {}
    ~Child() {}
};

class Parent : public QObject
{
    Q_OBJECT
public:
    explicit Parent(QObject *parent = 0): QObject(parent) {}
    ~Parent()
    {
        qDeleteAll(m_children);
    }
    void addChild(Child *ch)
    {
        m_children.append(ch);
    }    
private:
    QList<Child *> m_children;    
};

Parent 被删除时,使用addChild 方法添加到Parent 类的Child 实例将被删除。

以下用法会导致双子销毁:

int main()
{
    {
        Parent father;
        Child child;
        father.addChild( &child );
    }
    //child is out of scope now and gets destroyed
    //father gets destroyed too
    //father's destructor deletes child AGAIN!
    //crash!
    return 0;
}

如何通过使用智能指针来防止这种情况? QPointer 可用于 QObject 继承的类,这使得它在这种情况下毫无用处。我还能如何防止这种情况发生?

【问题讨论】:

  • 设计——这是对大多数终身问题的答案。对象的生命周期和所有权是设计的基本部分。
  • 能否请您为ParentChild 课程提供设计建议。请记住,Parent 类应该允许在我的设计中修改子项,例如添加、删除、编辑 Parent 类也有一个返回 m_children 的方法。
  • wuqiang 提供了一种可能是最简单的方法,不传递所有权是另一种方法(传递引用,内部复制)——尽管没有什么是真正的万无一失,但永远不要低估愚蠢的力量.

标签: c++ qt pointers memory-management smart-pointers


【解决方案1】:

这不是双重破坏。你不能删除堆栈对象。 相反,您应该在堆中分配它:

Parent father;
Child* child = new Child();
father.addChild( child );

【讨论】:

  • 我知道这是使用Parent 类的正确方法。但我正在寻找一种方法来更改Parent 类设计以使其万无一失!所以如果愚蠢的用户按照我的解释一切正常。
  • @user3559463,为了万无一失的设计,避免使用指针并通过值或 const 引用传递参数。
  • 但有时您必须接受并传递指针,例如当Child 足够大时,按值发送它不是一个好的解决方案。我想接受指针并意识到它们是否被破坏。
  • 如果你想让 unique_ptr 更健壮一点,你可以考虑将其作为参数,但我不确定这是否有意义(与愚蠢的竞赛是永远不可能的赢了)
【解决方案2】:

如果您查看constructor of QObject,您会注意到它需要一个 QObject 作为父对象,因此不要重新发明轮子,除非您已经在使用它(看起来您没有使用它),您可以使用 Qt 的父子层次结构:-

Parent* parent = new Parent;
Child* Child1 = new Child(parent);

然后您可以根据需要检索父级的children 列表,而不必担心管理子级,因为删除父级时它们会被删除。

如果要在创建后设置孩子的父母,可以调用孩子的setParent函数。

【讨论】:

  • ParentChild 这里的类与 QObject 父/子系统不同。将Parent 视为客户类,将Child 视为订单。
  • 很公平,但如果您的问题陈述了这一事实,您的问题将会得到改善。
【解决方案3】:

你可以使用std::tr1::shared_pointer here 是一个教程。如果您的编译器中没有该功能,您可以使用boost::shared_pointer,描述为here 您也可以使用implement 您自己的智能指针。

【讨论】:

  • 这完全改变了所有权语义,shared_ptr 经常被滥用为思考和设计的捷径......
【解决方案4】:

我猜你的第一个问题是关于子元素的所有权。 如果你想让 Parent 类处理 Child 的生命周期,你可以使用智能指针来解决它。

class Parent : public QObject
{
    using ptr = std::shared_ptr<Child>;
    Q_OBJECT
public:
    explicit Parent(QObject *parent = 0): QObject(parent) {}

    void addChild(const ptr& ch)
    {
        m_children.append(ch);
    }    
private:
    QList<ptr> m_children;    
};

然后:

{
    Parent father;
    auto child = std::make_shared<Child>();
    father.addChild( child );
}

如果您想保留实际使用情况,则必须制作副本并存储这些副本。

【讨论】:

  • 我猜用法应该是auto child = std::make_shared&lt;Child&gt;();
【解决方案5】:

Parent 拥有 Child 的所有权,而它不应该出现在您的示例中。

可能的解决方案包括:

  • Child 应在其析构函数中将其自身从其父级中移除,以防止错误删除。
  • 修改Parent接口,明确强制所有权转让:void addChild(std::share_ptr&lt;Child&gt; ch)void addChild(std::unique_ptr&lt;Child&gt; ch)
  • Parent 中维护 2 个容器,一个有所有权,一个没有所有权。

【讨论】:

  • 您的答案很好,因为包含更多解决方案,您可以添加@wuqiang 建议的其他解决方案,并通过@vahancho 和其他人建议的值传递Child。我可以按照@Kiroxas 建议的方式实现std::share_ptr,但@Lourens Coetzer 提到Child 可以在Parent 之后继续存在,std::unique_ptr 也一样吗?你能用std::unique_ptr给一个工作的sn-p吗?
【解决方案6】:

这是使用 std::shared_ptr 的可能解决方案:

    #include <iostream>
    #include <memory>
    #include <vector>

    class Child
    {
        public:
            Child(std::string name) 
            {
                name_ = name;
            }
            ~Child() 
            {
                std::cout << "~Child: " << name_ << std::endl;  
            }
        private:
            std::string name_;
    };

    class Parent 
    {
        public:

            ~Parent()
            {
                std::cout << "~Parent" << std::endl;    
            }

            void addChild(std::shared_ptr<Child> ch)
            {
                m_children.push_back(ch);
            }    
        private:
            std::vector <std::shared_ptr<Child> >  m_children;    
    };



    int main()
    {

        std::shared_ptr<Child> john (new Child("John")); 
        std::cout << "John created" << std::endl;

        {
            Parent father;

            {
                std::shared_ptr<Child> jack ( new Child("Jack")); 
                std::cout << "Jack created" << std::endl;
                father.addChild( john );
                father.addChild( jack );
                std::cout << "added John and Jack to father" << std::endl;
            }
            std::cout << "jack left scope" << std::endl;
            // jack is out of scope now but since it's a smart pointer 
            // and father still holds a reference to the memory it 
            // referred to that memory is still allocated.
        }
        std::cout << "father left scope" << std::endl;
        // father gets destroyed here and with it its m_children.  
        // As the list goes so does the last reference to the memory 
        // allocated for child and the child gets destroyed.  
        // Note that jack doesn't get destroyed here. Even though it was 
        // one of the children.  The reason is that the orphan smart 
        // pointer is still in scope.

        return 0;

}

注意:这不会让父级控制其子级的生命周期。 Parent 对象可以与 Child 对象相关联,但 Child 对象现在只有在对它的最后一个引用被销毁时才会被销毁。如果其他东西持有指向它的智能指针,就像上面显示的 john 一样,孩子就有可能“存活”其所有父母。如果您的意图是让 Child 只属于一个 Parent 并被其 Parent 破坏,则应将 Child 创建逻辑封装在 Parent 中,以便 Child 对象只能由 Parent 对象创建。

【讨论】:

  • 就像@Kiroxas 的回答,但更多的解释,谢谢
猜你喜欢
  • 1970-01-01
  • 2012-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多