//// РЕДАКТИРОВАТЬ # 2: Удалил всю предыдущую информацию и просто опубликовать рабочий код сейчас. Предыдущий вопрос стал слишком длинным:
#include <iostream>
#include <vector>
using namespace std;
template<class T>
class Node{
T data;
vector<Node<T>*> adjacent;
friend class Graph;
public:
int n;
Node(T initData) : data(initData), n(0){}
void addAdjacent(Node<T>& other){
adjacent.push_back(&other);
n++;
}
T getData(){
return data;
}
Node<T>* getEdge(int edgeNum){
return adjacent[edgeNum];
}
};
template<class T>
class GraphCl{
int n;
vector<Node<T>*> nodes;
T input;
public:
GraphCl(int size): n(size){
for (int i=0;i<n;i++){
cout << "Enter data for node " << i << ": ";
cin >> input;
nodes.push_back(new Node<T>(input)) ;
}
}
void addEdge(int baseNode, int edgeNode){
nodes[baseNode]->addAdjacent(*nodes[edgeNode]);
}
void printGraph(){
for (int i=0;i<n;i++){
Node<T> *base = nodes[i];
cout << "Data of node " << i <<": "<< base->getData() <<endl;
for (int j=0;j<base->n;j++){
cout << "Edge #"<< j+1 << " of node " << i << ": " << base->getEdge(j) <<endl;
}
}
}
};
int main(){
GraphCl<int> *myGraph = new GraphCl<int>(5);
myGraph->addEdge(0,1);
myGraph->addEdge(0,2);
myGraph->addEdge(0,3);
myGraph->addEdge(0,4);
myGraph->addEdge(3,1);
myGraph->addEdge(3,0);
myGraph->printGraph();
return 0;
}
Выход:
Enter data for node 0: -34
Enter data for node 1: 12
Enter data for node 2: 56
Enter data for node 3: 3
Enter data for node 4: 23
Data of node 0: -34
Edge #1 of node 0: 0x7fbeebd00040
Edge #2 of node 0: 0x7fbeebd00080
Edge #3 of node 0: 0x7fbeebe00000
Edge #4 of node 0: 0x7fbeebd000d0
Data of node 1: 12
Data of node 2: 56
Data of node 3: 3
Edge #1 of node 3: 0x7fbeebd00040
Edge #2 of node 3: 0x7fbeebd00000
Data of node 4: 23
Как видите, эта простая реализация работает. Я решил просто вырезать все сложные вещи и сохранить их простыми с динамически меняющимися векторами. Очевидно, менее эффективно, но я могу работать с этого момента. Поскольку я новичок в C ++, предыдущая реализация заставила меня развернуться на 360 градусов, размышляя о том, куда делись все указатели на указатели, даже не задумываясь о распределении памяти. Приведенный выше код в основном представляет собой ориентированный граф, который очень чувствителен к ошибкам ввода, поэтому я все еще работал над ним.
Спасибо за помощь!
Отвечая на обновленный вопрос
Есть несколько проблем с положением Node
непосредственно в std::vector
в твоем случае.
Используя std::vector
отлично подходит для многих вещей, но если вы делаете это, вы должны убедиться, что указатели на векторы не используются. Помните, что указатели ссылаются на точные адреса в памяти, где хранится объект. vector
это растущий контейнер элементов. Чтобы хранить элементы непрерывно, вектор выделяет кучу памяти, помещает туда объекты, и если он должен расти, он выделяет больше памяти и перемещает объекты вокруг. По сути, это делает что-то похожее на то, что вы делаете в своем Node
и расти (за исключением, в случае его явного уничтожения объектов перед освобождением старой памяти).
Обратите внимание, что ваш Grow
Функция выделяет новую память и копирует указатели. Одновременно векторы могут выделять новую память и копировать данные. Это означает, что удерживать указатели на данные в векторе плохо. Единственная гарантия vector
дает вам понять, что его данные будут по-прежнему доступны с использованием индексации в стиле массива, поиска, итерации и т. д., а не то, что данные будут существовать в одном и том же месте памяти навсегда.
Объясняя точную ошибку, которую вы видите
Вектор вызывает конструктор копирования. Конструктор копирования по умолчанию копирует каждое поле по одному. Это не то, что вы хотите в случае Node
потому что тогда у вас есть два вектора, которые думают, что они владеют Node** adjacent
место в памяти. Когда первый узел (старая копия) уничтожается, он освобождает смежные узлы (что совпадает с соседним узлом копии). Когда новая копия уничтожается, она попытается освободить ту же самую область памяти, но она уже освобождена. У вас также есть проблема в том, что, если вы попытаетесь получить доступ к памяти после того, как она была уничтожена на первом узле, у вас будут проблемы.
Почему эта ошибка появлялась, когда вы добавляли только узлы?
Когда вектор увеличивается до определенной величины, его необходимо изменить. В большинстве случаев процесс примерно такой:
Ваша ошибка появляется из-за шагов 2 и 3, в основном.
Исправление этой конкретной ошибки
Для вашего случая конструктор копирования по умолчанию не годится, потому что копирование узла должно соответствовать глубоко копия всех данных. Обычная копия в C ++ скопирует все данные в классе или самой структуре. Если данные являются указателем, то указатель копируется, а не объект, на который он указывает.
Переопределите конструктор копирования и оператор присваивания:
Node(const Node<T>& other) : data(other.data), capacity(other.capacity), n(other.n) {
adjacent = reinterpret_cast<Node**>(malloc(capacity * sizeof(Node**)));
memcpy(adjacent, other.adjacent, capacity * sizeof(Node**));
}
Node<T>& operator= (const Node<T>& other) {
data = other.data;
capacity = other.capacity;
n = other.n;
adjacent = reinterpret_cast<Node**>(malloc(capacity * sizeof(Node**)));
memcpy(adjacent, other.adjacent, capacity * sizeof(Node**));
}
Большая проблема
Большая проблема с вашим кодом заключается в том, что использование std::vector
а также указатели на его элементы. Выберите один из:
доступность
Относительно доступности массива к Graph
ближайшая вещь к текущей реализации — объявить Graph
как друг Node
, Просто добавьте:
friend Graph;
До конца Node
объявление класса.
Тем не менее, делая класс как friend
иногда это признак того, что API, который вы определили, не совсем верен, если классы должны знать слишком много о деталях реализации друг друга. В качестве альтернативы вы можете предоставить интерфейс для Node
такие как:
void AddAdjacent(Node* other);
Управление смежными узлами
Если вы хотите, чтобы ваш adjacent
массив указателей должен быть расширяемым, тогда вы в основном воссоздаете std::vector
так что я бы предложил использовать std::vector<Node*>
, Инициализация вектора с помощью конструктора по умолчанию (пустого) позаботится об этом, и nodes[baseNode]->adjacent.push_back(...)
будет все, что вам нужно в addEdges
,
Если память не учитывается, и у вас есть максимальное количество узлов в графе, вы можете создать экземпляр массива постоянного размера.
Если вы действительно не хотите использовать std::vector
, но вы на самом деле хотите растущий массив указателей, тогда вам придется управлять своими собственными malloc
а также free
звонки. Я напишу что-нибудь на этот счет, но мой совет — просто продолжать vector
,
Если вам интересно, подход с использованием массива будет выглядеть примерно так:
template<class T>
class Node : public Graph{
Node **adjacent; //pointer to array of POINTERS TO adjacent Nodes
int n;
size_t capacity;
T data;
friend Graph;
public:
Node(T initData) : data(initData), capacity(8) {
n = 0;
adjacent = reinterpret_cast<Node**>(malloc(capacity * sizeof(Node**)));
}
~Node() {
free(adjacent);
}
void Grow() {
size_t new_cap = base.capacity * 2;
Node<int> **copy = reinterpret_cast<Node<int>**>(malloc(new_cap * sizeof(Node**)));
memcpy(copy, base.adjacent, base.capacity); // copy and adjacent are non-overlapping, we can use memcpy
free(base.adjacent);
base.adjacent = copy;
base.capacity = new_cap;
}
};
И вставка:
Node<T>& base = nodes[baseNode];
Node<T>* edge = &(nodes[edgeNode]);
if (base.capacity == base.n) base.Grow();
base.adjacent[base.n++] = edge;