【问题标题】:Allocate vector of list of structs分配结构列表的向量
【发布时间】:2013-11-14 13:47:38
【问题描述】:

我有一个类,它的成员是结构列表的向量,我想动态分配列表和列表中的元素。我不知道为什么,但由于某种原因,即使我正在添加元素,我的列表似乎也是空的。

代码如下:

#include <iostream>
#include <vector>
#include <list>

using namespace std;

template <class Weight>
class Graph {
private:
    struct Edge {
        int node;
        Weight weight;
    };

    vector<list<Edge>> nodes;
public:
    Graph(int numberOfNodes) {
        int i;
        for (i=0; i < numberOfNodes; ++i) {
            nodes.push_back(*(new list<Edge>));
        }
    }

    void addEdge(int nodeA, int nodeB, Weight weight, bool bothWays= false) {
        if (bothWays) {
            addEdge(nodeB, nodeA, weight);
        }
        list<Edge> edgesA= nodes.at(nodeA);
        Edge *edge= new Edge;
        edge->node = nodeB;
        edge->weight = weight;
        edgesA.push_back(*edge);
    }


    void print() {
        unsigned int i;
        list<Edge> edges;
        for(i=0; i < nodes.size(); ++i) {
            cout << "Node " << i << ". Edges: ";
            edges= nodes.at(i);
            typename list<Edge>::iterator iterator = edges.begin();
            typename list<Edge>::iterator end = edges.end();
            for (; iterator != end; ++iterator) {
                cout << "Node " << iterator->node << ". Weight: " << iterator->weight;
            }
            cout << endl;
        }
    }
};


Graph<int> generateRandomGraph(int numberOfNodes) {
    Graph<int> g(numberOfNodes);
    int i, j, weight=22;
    for(i=0; i < numberOfNodes; ++i) {
        for(j=i; j < numberOfNodes; j++) {
            g.addEdge(i, j, weight, true);
        }
    }
    return g;
}


int main() {
    Graph<int> g= generateRandomGraph(3);
    g.print();
}

它正在打印这个:

Node 0. Edges: 
Node 1. Edges: 
Node 2. Edges: 

好像列表是空的。试图使用调试来找出问题所在,但没有运气。我来自 ANSI C 背景,但我仍然不确定 new 关键字是如何工作的。我应该在我的向量中存储一个指向列表的指针吗?我应该在我的列表中存储一个指向我的结构的指针吗?

如果答案还可以提供如何在类的析构函数上释放内存,我将不胜感激。

注意:该模板是为了让我的 Graph 上的权重可以定义为 int 或 float。

编辑:我只想补充一点,我之前没有使用任何硬编码指针。您在此处看到的代码是我经过几个小时的反复试验后得到的。

【问题讨论】:

  • 这段代码有很多内存管理问题。对于初学者,Graph 构造函数中存在内存泄漏。没有理由使用您编写的代码在堆上分配list(也不是Edge)实例。

标签: c++ list memory-management vector


【解决方案1】:

正如 cmets 中所述,这里的内存管理存在许多问题,但边列表保持为空的原因是 addEdge(...) 中的代码。

如果你把它改成这样:

void addEdge(int nodeA, int nodeB, Weight weight, bool bothWays= false) {
    if (bothWays) {
        addEdge(nodeB, nodeA, weight);
    }
    Edge edge;
    edge.node = nodeB;
    edge.weight = weight;
    nodes.at(nodeA).push_back(edge);
}

您将摆脱其中一个内存管理问题,并拥有一个边列表。为清楚起见,前面的代码复制了边列表,并将新边添加到副本中,而不是原始列表。

我可以看到的另一个内存管理问题是在构造函数中。这是一个没有内存管理问题的音译。还有其他方法可以使用vector 上的resize(...) 方法来执行此操作,这些方法涉及更少的代码(留作练习):

Graph(int numberOfNodes) {
    int i;
    for (i=0; i < numberOfNodes; ++i) {
        nodes.push_back(list<Edge>());
    }
}

还有几件事我会做不同的事情,但您可能希望将其带到 codereview.stackexchange.com 进行完整审查。

【讨论】:

    【解决方案2】:
    Graph(int numberOfNodes) 
    {
        int i;
        for (i=0; i < numberOfNodes; ++i) 
        {
            //nodes.push_back(*(new list<Edge>));
                                ^^^^^^^^^^^^^^ memory leak
           nodes.push_back(list<Edge>()); // adds an empty list to the vector
        }
    }
    

    这修复了内存泄漏 #1。

    list<Edge> edgesA= nodes.at(nodeA);
    Edge *edge= new Edge;
    edge->node = nodeB;
    edge->weight = weight;
    edgesA.push_back(*edge);
                     ^^^^^ memory leak
    

    修复它:

    list<Edge> edgesA= nodes.at(nodeA);
    Edge edge; // no need for heap allocation!
    edge.node = nodeB;
    edge.weight = weight;
    edgesA.push_back(edge);
    

    iterator 是迭代器变量名称的可怕名称。

    void print() 
    {
        std::for_each(nodes.begin(), nodes.end(), [](const list<Edge>& l)
        {
            std::for_each(l.begin(), l.end(), [](const Edge& e)
            {
                std::cout << "Node " << e.node << ". Weight: " << e.weight;
            });
            std::cout << std::endl;
        });
    }
    

    这应该可以解决您的输出问题。

    您还对列表的副本而不是实际列表进行操作:

    list<Edge> edgesA= nodes.at(nodeA);
    

    您应该将其声明为:

    list<Edge>& edgesA= nodes.at(nodeA);
    //        ^^ This makes it a reference instead of a copy
    

    【讨论】:

    • 我做了您的更改,但它仍然没有打印任何内容。我知道内存泄漏,实际上我正在按照您的描述进行操作(不使用原始指针),但它不起作用。
    • 是的,现在它正在工作,谢谢。我已经习惯了 Java,其中所有不是原始的东西都是参考。我知道如何在函数声明中使用 & 作为“传递引用”,或者从值中获取指针(例如 int j=5; int *i= &j; ),但我从未在声明中看到它.
    • 当我做 Graph g(numberOfNodes);在 generateRandomGraph 中我也应该在这里使用参考吗? Graph 对象在堆栈中分配,然后我将其返回给主函数。 generateRandomGraph 中的 Graph 对象是否被复制到 main 中的 Graph 对象中(由于图可能很大,因此会浪费大量资源)?在这种情况下我是否应该使用 new 关键字将 Graph 分配到堆中?
    • 您不会(实际上也不能)从generateRandomGraph 函数返回引用。您正在返回一个副本(很可能会被忽略),它将存储在您的 g 变量中的 main 中。
    • 所以避免重复的正确方法是让我的函数像这样:void generateRandomGraph(Graph& g, int numberOfNodes),接受对图形对象的引用并修改它,对吗?
    猜你喜欢
    • 2020-06-20
    • 2014-04-03
    • 1970-01-01
    • 2012-12-05
    • 2015-05-17
    • 2014-06-26
    • 1970-01-01
    • 2021-12-26
    • 2023-03-28
    相关资源
    最近更新 更多