c++ 连续调用返回类成员引用的函数后指针的意外行为

dw1jzc5e  于 2023-03-14  发布在  其他
关注(0)|答案(2)|浏览(147)

这里是C++初学者。我读到过返回对函数的局部变量的引用是不好的做法,因为当函数返回时,它超出了作用域。我理解这是为什么,但我想知道是否可以返回对类成员的引用。
我正在编写一个简单的图实现,它是一个邻接表,有一个函数insertNode,它将一个Node对象添加到Graph类的std::vector<Node>类型的成员变量中,并返回一个对刚添加到向量中的节点的引用。
insertNode的实现是:

//graph.cpp
Node* Graph::insertNode(Node n) {
    this->nodeArray.push_back(n);
    return &(this->nodeArray.back());
}

其中Graph类为:

//graph.h
class Graph {
private:
    vector<Node> nodeArray; 
public:
    // Constructor
    Graph();
    Node* insertNode(Node n);
    vector<Node> getNodes(); 
    void connectNodes(Node* a, Node* b, int edgeWeight);
};

节点类为:

//node.h
class Node {
private:
    list<Edge> edgeList;
    string nodeName; 
    pair <int, int> coordinates;
public:
    Node();
    Node(string nodeName, int x, int y);
    void setNodeName(string nodeName);
    void setXY(int x, int y);
    void insertEdge(Edge edgeToAdd);
    void removeEdge(Edge fromEdge, Edge toEdge);
    list<Edge> getEdgeList();
};

我用这种方式实现了insertGraph,因为我需要返回一个指向节点的指针,该节点是为了在connectNodes中使用而添加的,connectNodes是一个在两个节点之间创建Edge对象并更新每个节点上的边列表的函数,其中边的源节点和目的节点只是指向节点本身的指针。
我遇到的问题是,在以下代码(作为测试用例的一部分)中,当我第一次调用insertNode时,我可以在IDE的监视器中看到它返回指向所添加节点的指针,并且此地址的内容是正确的,但是在使用不同的Node对象连续调用insertNode时,返回的第一个指针似乎被修改了。

//test.cpp
SECTION("connects two nodes with an edge") {
        Graph g = Graph();
        Node a = Node("Johannesburg", 5, 10);
        Node b = Node("Cape Town", 26, 10);

        Node* aptr = g.insertNode(a); //first call
        Node* bptr = g.insertNode(b); //second call
        g.connectNodes(aptr, bptr, 1);

在第一次调用insertNode传递a之后,aptr看起来如下所示:

在第二次调用insertNode传递b之后,aptr更改为:

为什么会出现这种行为?我犯了糟糕的C++代码中的哪一个原罪?如果这确实是问题所在,那么实现insertNode函数的更好方法是什么?

pkwftd7m

pkwftd7m1#

std::vector不是基于节点的容器;因此,在push_backemplace_back调用之后,如果新的size超过旧的capacity,则会发生向量的完全重新分配,从而导致先前引用该向量的所有指针和迭代器失效。由于您使用的是向量(具有连续内存分配),因此您可以受益于其随机访问属性并将索引(而不是指针)存储到元素:

std::size_t Graph::insertNode(Node n) {
    this->nodeArray.push_back(n);
    return size(this->nodeArray);
}

因为索引不会因为向量增长而失效;并且可以对照size检查它们以确保在可能的收缩操作(eraseremoveclear...)的情况下的有效性。因为即使向量可以增长到最大索引值,到最后一个元素的索引仍然比其大小小1。

pvcm50d1

pvcm50d12#

向量为一些对象分配空间。这被称为capacity。当你push_back()一个项目时,向量的size会改变,但capacity不会改变。然而,在某个时候,capacity将不再足以容纳push_back ed项目,向量将需要分配新的内存。
它会分配更多的内存,这样capacity会再次大于size。有些实现每次会将内存量增加一倍,有些则增加50%。在有足够的存储空间后,向量会将对象从旧位置移动到新位置。因此,它们的地址改变了,旧的存储空间是free d或delete d,因此用你保留的指针访问那个位置是未定义的行为。
您可以重新考虑您的算法,或者使用指向Nodes的指针向量,以便移动的不是实际的节点,而是它们的指针。

相关问题