I'm implementing a graph with C++. After reading a C++ tutorial, I thought that any object defined locally will only live in the most local scope. Say we define an int i
in function f
; outside the function, i
is no longer a valid object. As far as I know, variable i
exists in the stack of function f
. Once f
is finished, its stack gets removed, and all the objects created inside will be destroyed (unless dynamically allocated, in which case they exist in the heap).
But somehow when I'm implementing a member function, addEdge
, which is supposed to receive input of a reference of an edge (a user defined object), create another edge that has vertices swapped to indicate backward direction, and use another member function addEdgeOneWay
to add this newly created Edge
to an adjacency list. I thought this edge f
created in function addEdge
is not going to live after function is finished and thus, when the addEdgeOneWay
function is called f
will no longer exist and the program will crash due to dangling reference. But it didn't! It passed the test cases:
SECTION("add edges 2 ways to graph"){
Graph<int> g{};
//add 3 vertices 0, 1, 2 to graph
int dummyContent = -1;
g.addVertex(dummyContent);
g.addVertex(dummyContent);
const Edge e {0, 1};
g.addEdge(e);
REQUIRE(g.getAdjList()[0].size() == 1);
REQUIRE(g.getAdjList()[1].size() == 1);
REQUIRE((*g.getAdjList()[0].begin()).getThisVertex() == 0);
REQUIRE((*g.getAdjList()[0].begin()).getThatVertex() == 1);
REQUIRE((*g.getAdjList()[1].begin()).getThisVertex() == 1);
REQUIRE((*g.getAdjList()[1].begin()).getThatVertex() == 0);
}
template <typename T>
class Graph{
public:
//default constructor, only null graph can be constructed
Graph() {}
Graph(int numVertices, std::vector<T> &vertices)
: m_numVertex{numVertices}
, m_vertexList{vertices}
{}
//only add edge from this vertex to that vertex
void addEdgeOneWay(const Edge& e){
int u {e.getThisVertex()};
int initSize{static_cast<int>(m_adjList[u].size())};
m_adjList[u].insert(e);
int finalSize{static_cast<int>(m_adjList[u].size())};
m_numEdge += (finalSize - initSize);
}
void addEdge(const Edge& e){
this->addEdgeOneWay(e);
const Edge f{e.getThatVertex(), e.getThisVertex(), e.getWeight()};
this->addEdgeOneWay(f);
}
I wonder why this works? Somehow after adding this local edge to graph's adjacency list (a vector of sets), it lives out of scope. I wonder if it has anything to do with temporary object, or something else.
Actually, your local object f
is copied into the set and this copy survives the destruction of the local object since it lives until the set itself is destroyed.
If you have doubts, you can track constructor/destructor calls by instrumenting them. Here is a simplified example:
#include <iostream>
#include <set>
struct Edge
{
int value=0;
Edge(int v) : value(v) { std::cout << "user constructor " << this << "\n"; }
Edge() { std::cout << "default constructor " << this << "\n"; }
Edge(Edge const& e) : value(e.value) { std::cout << "copy constructor (const&) " << this << " from " << std::addressof(e) << "\n"; }
Edge(Edge && e) : value(e.value) { std::cout << "copy constructor (&&) " << this << "\n"; }
~Edge() { std::cout << "destructor " << this << "\n"; }
// We need this operator for using Edge with std::set
bool operator< (Edge const& other) const { return value<other.value; }
};
// We define a set of Edge.
std::set<Edge> edges;
auto addEdge ()
{
std::cout << "Entering addEdge\n";
// We create an Edge instance that lives during execution of 'addEdge'
std::cout << "Creating edge\n";
Edge e (123);
// We add the edge into the set, which will copy 'e
std::cout << "Inserting edge\n";
edges.insert (e);
std::cout << "Leaving addEdge\n";
}
int main()
{
std::cout << "Entering main\n";
addEdge();
std::cout << "Leaving main\n";
}
that will produce something like
Entering main
Entering addEdge
Creating edge
user constructor 0x7ffc59030fdc
Inserting edge
copy constructor (const&) 0xcb0c6e0 from 0x7ffc59030fdc
Leaving addEdge
destructor 0x7ffc59030fdc
Leaving main
destructor 0xcb0c6e0
Notes:
0x7ffc59030fdc
is built by the user constructor on the line Edge e (123)
0xcb0c6e0
is copied from 0x7ffc59030fdc
when inserted into the set0x7ffc59030fdc
(i.e. objet e
local to function addEdge
) is destroyed when leaving addEdge
0xcb0c6e0
is destroyed when leaving the main
function, i.e. when the set edges
is destroyed.Note that you will have a copy because the object isn't already contained in the set, so if you try to insert it again, no new copy will be made.
auto addEdge ()
{
std::cout << "Entering addEdge\n";
// We create an Edge instance that lives during execution of 'addEdge'
std::cout << "Creating edge\n";
Edge e (123);
// We add the edge into the set, which will copy 'e
std::cout << "Inserting edge 1\n";
edges.insert (e);
std::cout << "Inserting edge 2\n";
edges.insert (e);
std::cout << "Leaving addEdge\n";
}
However, if you change the value of the object and insert it into the set, a second copy will be done:
auto addEdge ()
{
std::cout << "Entering addEdge\n";
// We create an Edge instance that lives during execution of 'addEdge'
std::cout << "Creating edge\n";
Edge e (123);
// We add the edge into the set, which will copy 'e
std::cout << "Inserting edge 1\n";
edges.insert (e);
std::cout << "Inserting edge 2\n";
e.value = 456; // we modify the content of the object
edges.insert (e);
std::cout << "Leaving addEdge\n";
}