循环数据依赖析构函数

时间:2016-09-02 11:13:53

标签: c++ oop c++11 graph

我现在正在设计自己的带有邻接列表的图形类。除了析构函数之外,我完成了大部分步骤。

这是我的Vertex课程:

struct Vertex{
public:
    Vertex(){m_name="";}
    Vertex(string name):m_name(name){}
    ~Vertex(){
        cout << "vertex des" << endl;
        for(int i = 0; i < m_edge.size(); i++){
            delete m_edge[i];
            m_edge[i] = nullptr;
        }
    }
    string m_name;
    vector<Edge*> m_edge;
};

这是我的Edge类:

struct Edge{
public:
    Edge() : m_head(nullptr), m_tail(nullptr) {m_name="";}
    Edge(string name) : m_name(name), m_head(nullptr), m_tail(nullptr) {}

    ~Edge(){
        cout << "Edge des" << endl;

        delete m_head;
        m_head = nullptr;
        delete m_tail;
        m_tail = nullptr;
    }

   string m_name;

   Vertex* m_head;
   Vertex* m_tail;
};

但是,我注意到在调用析构函数时,两个类实际上都调用了彼此的析构函数,所以这给了我一个无限循环。这个设计有问题吗?如果没有,有没有办法解决这个析构函数问题?谢谢!

4 个答案:

答案 0 :(得分:4)

  

然而,我注意到在调用析构函数时,两个类实际上都会相互调用析构函数,所以这给了我一个无限循环。这个设计有问题吗?

您当前的设计确实存在问题。动态分配应仅由其各自的所有者删除。通常,所有者是创建对象的人,通常只有一个所有者。如果有多个对象,则所有权为 shared 。共享所有权需要一种机制 - 例如引用计数 - 来跟踪当前的所有者数量。

根据目标来判断,你的顶点似乎是拥有的&#34;通过多个边缘,边缘看起来由多个顶点拥有。如果不是这样,那么你的图表会很无聊。但您尚未实施任何形式的所有权跟踪。

我建议使用更简单的设计,其中边缘不具有顶点,顶点也不具有边缘。它们都应该由父对象拥有,可能称为Graph

答案 1 :(得分:2)

由于问题标记为C ++ 11,因此应首先使用托管指针。在托管指针中,weak_ptr可以帮助您打破循环依赖:

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

using namespace std;

struct Edge;

struct Vertex{
public:
    Vertex(){m_name="";}
    Vertex(string name):m_name(name){}
    ~Vertex(){
        cout << "vertex des" << endl;
        for(auto e : m_edge)
        {
            if(e->m_head.lock().get() == this)
            {
                e->m_head = nullptr;
            }
            if(e->m_tail.lock().get() == this)
            {
                e->m_tail = nullptr;
            }
        }
    string m_name;
    vector<shared_ptr<Edge>> m_edge;
};

这里你的原始指针已被更改为shared_ptr s:无需在销毁时调用delete,但是你应该告诉边缘忘记顶点(参见下面的head and tail声明)。

struct Edge{
public:
    Edge(){m_name="";}
    Edge(string name):m_name(name){}

    ~Edge(){
        cout << "Edge des" << endl;
        // if you're here, this means no vertices points to the edge any more:
        // no need to inform head or tail the edge is destroyed
    }

    void do_something_to_head()
    {
        auto head = m_head.lock();
        if(head)
        {
            head->do_something();
        }
    }

   string m_name;

   weak_ptr<Vertex> m_head;
   weak_ptr<Vertex> m_tail;
};

weak_ptr更改了边缘中的原始指针:它们是&#34;非拥有&#34;指向共享资源的对象。您无法直接取消引用weak_ptr:您必须调用方法lock,如果它存在,则为指向的资源创建临时shared_ptr(从而防止循环依赖)。用法:

int main()
{
    shared_ptr<Vertex> v1 = make_shared<Vertex>();
    shared_ptr<Vertex> v2 = make_shared<Vertex>();

    // connection should be encapsulated somewhere

    shared_ptr<Edge> e = make_shared<Edge>();

    v1->m_edge.push_back(e);
    e->m_head = v1;

    v2->m_edge.push_back(e);
    e->m_tail = v2;

    return 0;
}

答案 2 :(得分:1)

我认为,这是一个设计问题,因为在图形术语中 - 当您删除Edge时 - 您不应该删除其顶点。

我想,

m_head = nullptr;
m_tail = nullptr;

就足够了。

答案 3 :(得分:1)

与其他人一样,对你的问题的简短回答是:是的,调用everyover的析构函数是有问题的,因为这可能会导致未定义的行为

例如,看看这种情况:

  • 正在删除Vertex对象v
  • 会导致删除成员向量Edge中的第一个m_edge
  • 导致删除m_headm_tail Vertex
  • 假设其中一个是v,那么v的析构函数中的下一个循环将尝试访问已删除的数据!!!

充其量,您的程序将会出现段错误;最坏的......谁知道......

你的设计并不坏。然而,它的问题是你无法清楚地定义所有权(这可能有助于知道谁应该摧毁谁)。

确实,假设Vertex可以与多个(和至少一个)Edge相关,并且Edge与恰好两个Vertex有关,那么您可以认为Edge由一对Vertex拥有。在那种情况下,管理删除顺序并不容易......

然而,你并不一定需要一个所有权关系来指出谁应该摧毁谁。如上所述,Edge恰好与两个Vertex相关;如果其中一个被销毁,那么Edge也应该被销毁。另一方面,如果Edge被销毁,则没有理由销毁与其相关的任何Vertex,因为每个Edge仍可能与其他现有Vertex相关;唯一的例外是当Edge与任何struct Edge; struct Vertex { public: // ctors unchanged ~Vertex(); // implemented below void remove_relation(Edge* edge) // for use by Edge only { std::vector<Edge*>::iterator it = std::find(m_edge.begin(), m_edge.end(), edge); if (it != m_edge.end()) m_edge.erase(it); if (m_edge.size() == 0) delete this; // this Vertex can be safely deleted } string m_name; vector<Edge*> m_edge; }; struct Edge { public: // ctors unchanged ~Edge() { // Only have to remove relation with m_head & m_tail if (m_head) m_head->remove_relation(this); if (m_tail) m_tail->remove_relation(this); std::cout << "deleted Edge " << this << std::endl; } void delete_from_vertex(Vertex* from) // for use by Vertex only { // Prevent from removing relation with the calling Vertex if (m_head == from) m_head = nullptr; else if (m_tail == from) m_tail = nullptr; else assert(false); // Vertex not in relation with this Edge delete this; // finally destroy this Edge } string m_name; Vertex* m_head; Vertex* m_tail; }; Vertex::~Vertex() { for(int i = 0; i < m_edge.size(); i++) m_edge[i]->delete_from_vertex(this); // special destruction std::cout << "deleted Vertex " << this << std::endl; } 没有更多关系时。遵循这些规则的代码如下:

folder

Live example