Einfügen in Vector

Khale

Grünschnabel
Hallo zusammen,

ich habe hier ein etwas umfangreicheres Projekt in C++ und hadere gerade etwas mit Zeigern und Referenzen im Zusammenhang mit Vectoren. Ja, bin ein Java-"Kind" :/
Meine Situation: Ich habe eine Klasse A, die zwei Vectoren enthält. Einer ist vom Typ A, der andere vom Typ B (enthält eigentlich nur den Namen des Objekts). In einer Klasse C wird daraus ein Baum aufgebaut (A sind die inneren Knoten, B die Blätter). Soweit funktioniert das ganz gut, allerdings werden in meinen Vectoren die falschen Werte abgespeichert - vermutlich weil ich die Zeiger falsch verwende.
Hier die relevanten Parts des Codes:

C++:
//A.h
class A
{
public:
//... div. Funktionen und Konstruktoren
void addChildB(B* b);
void addChildA(A* a);

private:
  std::vector<B> childsB;
  std::vector<A> childsA;
  int type;
}
C++:
//A.cpp
/* Diese Funktion übernimmt das Einfügen in den Vektor */
void A::addChildB(B* b)     //analog gibt es dazu nochmal dieselbe für addChildA...
{
   childsB.push_back(*b);
}
C++:
//C.cpp

void C::baueBaum(B currNode)    //analog gibt es dazu nochmal dieselbe für addChildA...
{
   // ...
   string name("a");
   B newB = *new B(name);
   currNode->addChildB(&newB);
   // ...
}
Soweit dazu, ich hoffe das ist alles nötige. Wenn nicht, zeig ich gern noch mehr.
Wenn ich nun mit dem nächsten Snippet versuche, mir Attribute der Vectorelemente ausgeben zu lassen, bekomme ich für type = -842150451. type bewegt sich allerdings nur zwischen 0 und 5 (zur Unterscheidung verschiedener Typen von A-Objekten)

C++:
/* Diese Funktion gibt den Baum aus (ist nur ein Fragment der Funktion) */
void print(A rt)
{
  for(size_t i = 0; i < rt.getChildsA().size(); ++i)
  {
     if( i == 0 ) std::cout << off << "\nChilds of class A: ";
     std::cout << off << rt.getChildsA().at(i).getTypeAsInt() << "  ";
   }
}

Achja: includes usw. habe ich mal weggelassen, die sollten allerdings in Ordnung sein.
 
Hi und Willkommen bei tutorials.de,

C++:
void C::baueBaum(B currNode)    //analog gibt es dazu nochmal dieselbe für addChildA...
{
   // ...
   string name("a");
   B newB = *new B(name);
   currNode->addChildB(&newB);
   // ...
}
Hier gibt es zwei bzw. drei Probleme.

a) Der Parameter currNode, gleich in der ersten Zeile:
Ohne Pointer/Referenzen/wasauchimmer werden Parameterwerte in C++ immer als Kopie übergeben.
Dh. in der Funktion wird mit addChildB nur eine Kopie verändert, die am Ende von baueBaum wieder weg ist.
(Eine mögliche) Lösung ist eben, den Parameter auch als Pointer oder Referenz zu übergeben.

Nicht direkt relevant, aber paar Nebeninfos:
Eine Kopie übergeben, wenn drin nichts geändert wird, ist theoretisch ok, kann aber je nach Situation auch nachteilig sein: Es dauert länger (wenn man an Geschwindigkeitsoptimierung interessiert ist), braucht natürlich auch Speicher für die Kopie (wenn das eine Objekt 1GB hat kann das schon Probleme machen)... man muss aber nicht immer künstlich Pointer nehmen. Falls du dich mehr mit C++ auseinandersetzt, informier dich irgendwann in der Zukunft über Move semantics, R-Value referencen, smart pointer... Irgendwann. Das Thema gründlich durchgenommen ist nicht gerade das Einfachste

Und wenn die Klassenobjekte (von B in dem Fall) innen selber Pointer haben ist es wichtig, was mit dem passiert, worauf die zeigen. Bei der normalen automatischen Parameterkopie werden zwar die Adressen kopiert, aber der Pointer vom Original und der Pointer von der Kopie zeigen beide auf das selbe Ding (also zwei Objekte auf ein Pointerziel). Je nachdem, was es ist, will man aber den Pointerinhalt mitkopieren lassen, sowas kann man im Copy-Konstruktor festlegen. Was ein Konstruktor ist ist vermutlich klar, Konstruktoren können Parameter haben ... und ein Konstruktor, der eine Referenz auf ein anderes Objekt der eigenen Klasse nimmt, wird (wenn vorhanden) zum intern auch zum Erstellen der Kopie verwendet. Da kann man dann genau festlegen, was mit welcher Variable in der Klasse gemacht werden soll.

b) und c)
In der Zeile mit newB, also
B newB = *new B(name);
legst du keinren Pointer mit einem neu allokierten Objekt an, sondern machst ein neues Objekt, kopierst es in die lokale Variable newB, und "vergisst" das Original. Folgender Code wäre sinnvoller:
B *newB = new B(name);
Also eine Variable zum Adress-speichern statt ein ganzes B, und da die Adresse vom ge-new´ten Objekt rein.
Was sonst passiert wäre: Von den zwei erstellen Objekten nimmst du das in newB zum Hinzufügen (bzw. einen Pointer dahin), nur ist newB am Ende von baueBaum auch wieder weg (wie jede lokale normale Variable). Der Pointer im vector weißt das nicht und zeigt auf Speicher, der später schon für was Anderes verwendet werden kann, sobald man wieder darauf zugreifen will. zB. deswegen die seltsame Zahl bei der Ausgabe.

Und was mit dem alten Code auch passiert wäre: Das ge-new´te Objekt wird zeurst nach newB kopiert und dann "vergessen" = man hat keinen Pointer etc. mehr dahin, wo es ist. Aber es exisitert noch und braucht Speicher. Einen Garbage collector gibts in C++ nicht... Generell muss alles, was mit "new" angelegt wurde, irgendwann mit "delete" wieder gelöscht werden (vielleicht auch für den Rest von deinem Programm relevant...). Die oben erwähnte Smartpointer machen die Sachen bequemer, aber für "rohe" Pointer muss das gemacht werden.
 
Zuletzt bearbeitet:
Hi,
vielen Dank für die fixe Antwort. Die Funktion hatte ich tatsächlich falsch abgetippt, es müsste void C::baueBaum(B* currNode) sein. Ja, die Zeigerei in C/C++ ist noch mein großes Problem, vielen Dank also für die Hinweise und Erläuterungen :)
Der Copy-Konstruktor ist übrigens für Klasse A auch da, ich habe den aber unterschlagen.
Was die Lebensdauer der in baueBaum erzeugten Objekte betrifft: Wie bekomme ich die jetzt dauerhaft erzeugt? Ich möchte mit meinem Baum ja später noch tolle Dinge machen (was mit abgesägtem Ast natürlich nicht geht). Ich habe vor meinem Post hier schon von reference_wrapper gelesen, wäre das nicht die Lösung? Also statt
C++:
  std::vector<B> childsB;
  std::vector<A> childsA;
C++:
  std::vector<reference_wrapper<B>> childsB;
  std::vector<reference_wrapper<A>> childsA;
und dazu eben die nötigen "Umbauten"?
 
Ah, die Vektoren speichern ja keine Pointer, kurz falsch gedacht

Ist B irgendwie in Vererbungszeug verwickelt oder hat es Pointer in sich?

Ist es wichtig, dass in den Vektoren genau die Objekte gespeichert werden, von denen man zuerst den Pointer hat (weil man zB. den Pointer später noch verwendet und das DIng im Vektor eben sich "mitändern" soll) oder ist das egal?

Und dritte Frage, soll es einfach zu programmieren oder möglichst effizient sein?
 
Vererbungszeug: Ja. Es gibt noch eine gemeinsame Oberklasse von A und B, nennen wir sie D. D vererbt an die beiden allerdings nur einen einzigen einfachen boolean.
Die Objekte in den Vektoren sollen noch änderbar sein, eben per Zugriff über die Vektoren, wolltest du das wissen? Die Pointer in baueBaum() sind da eigentlich egal, solange das geht.
Effizienz ist eher nebensächlich, in erster Linie soll es funktionieren. Die Anwendungsfälle können auch nicht großartig ausarten. Im schlimmsten Fall würden die Vektoren wohl nicht mehr als etwa 20 Elemente haben.
 
Also, man könnte das Ganze dann auch ohne Pointer machen.
Beim Parameterübergeben wird zwar ein paar mal sinnlos kopiert, aber wenns kein Geschwindigkeitswettrennen ist...
C++:
//A.h
class A
{
public:
//... div. Funktionen und Konstruktoren
void addChildB(B b);
void addChildA(A a);

private:
  std::vector<B> childsB;
  std::vector<A> childsA;
  int type;
}
C++:
//A.cpp
/* Diese Funktion übernimmt das Einfügen in den Vektor */
void A::addChildB(B b)     //analog gibt es dazu nochmal dieselbe für addChildA...
{
   childsB.push_back(b);
}
C++:
//C.cpp

void C::baueBaum(B currNode)    //analog gibt es dazu nochmal dieselbe für addChildA...
{
   // ...
   string name("a");
   B newB(name);
   currNode->addChildB(newB);
   // ...
}
C++:
/* Diese Funktion gibt den Baum aus (ist nur ein Fragment der Funktion) */
void print(A rt)
{
  for(size_t i = 0; i < rt.getChildsA().size(); ++i)
  {
     if( i == 0 ) std::cout << off << "\nChilds of class A: ";
     std::cout << off << rt.getChildsA().at(i).getTypeAsInt() << "  ";
   }
}
 
Aber die Variante sollte ja ähnlich zu meinem Ausgangsproblem sein, zumindest werden mir beim print() keine Elemente mehr ausgegeben, da die Länge der Vektoren jeweils 0 ist. Habe ich mich irgendwo missverständlich ausgedrückt?
 
Also, missverständlich warst du nicht, und eigentlich sollte dadurch nichts kaputtgehn.
Kannst du den aktuellen Code zeigen? Vllt. hilft das ja
 
Die relevanten Parts sind alle auf dem Stand, den du vorhin gepostet hast. Kaputtgegangen ist auch nichts - es "speichert" halt die Objekte nicht.
 

Neue Beiträge

Zurück