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

问题描述 投票:0回答:2

C++ 初学者在这里。我读过,返回对函数局部变量的引用不是好习惯,因为它在函数返回时超出范围。我明白这是为什么,但我想知道是否可以返回对班级成员的引用。

我正在编写一个作为邻接表的图的简单实现,并有一个名为

insertNode
的函数,它将一个
Node
对象添加到类
std::vector<Node>
类型的成员变量中
Graph
并返回对刚刚添加到向量中的节点。

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
- 一个在两个节点之间创建
Edge
对象并更新边缘列表的函数边缘的源节点和目标节点的每个节点只是指向节点本身的指针。

我遇到的问题是,在下面的代码中,作为测试用例的一部分,当我第一次调用

insertNode
时,我可以在我的 IDE 手表中看到它返回指向添加的节点的指针以及此处的内容地址是正确的,但是在连续调用
insertNode
时采用不同的 Node 对象,返回的第一个指针似乎被修改了。

//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
函数的更好方法是什么?

c++ list pointers
2个回答
0
投票

Vector 为一些对象分配空间。这就是所谓的

capacity
。当你
push_back()
一个项目时,向量的
size
会改变但不会
capacity
。然而,在某些时候,
capacity
将不再足以容纳
push_back
ed 项,向量将需要分配新内存。

它会分配更多的内存,让

capacity
再次大于
size
。一些实现每次都将数量翻倍,而另一些则增加 50%。在有足够的存储可用后,向量会将对象从它们的旧位置移动到新位置。因此,他们的地址发生了变化。旧的存储空间是
free
d 或
delete
d,因此使用您保留的任何指针访问该位置是未定义的行为。

您可以重新考虑您的算法或使用指向节点的指针向量,这样移动的不是实际节点,而只是它们的指针。


0
投票

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

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

因为索引不会因向量增长而失效;并且可以根据

size
检查它们以确保在可能的收缩操作(
erase
remove
clear
...)的情况下的有效性。此外,始终无效的大小将是
std:: numeric_limits<std::size_t>::max()
;因为即使向量可以增长到最大索引值,最后一个元素的索引仍然比它的大小小 1。

© www.soinside.com 2019 - 2024. All rights reserved.