
Zitat von
Feuerstern
Die Objekt befinden sich alle Zusammen in einem Vektor der dann Zeiger vom Typ Base speichern kann (Von denen die Objekte hinter dem Zeiger aber alle entweder vom Typ ChildA oder ChildB sind).
Nur als Hinweis: Wenn man später die Zeiger auf die Objekte aus dem std::vector entfernt und nicht mehr benötigt, dann muss man diese später auch wieder mit delete freigeben, sonst schafft man sich ein Speicherleck. Aussehen müsste das dann in etwa so:
Code:
std::vector<Base*> objekte;
Base *a = new ChildA();
Base *b = new ChildB();
objekte.push_back(a);
objekte.push_back(b);
/* Mache hier irgendwas mit dem Vektor. */
//Vektor leeren und Objekte freigeben
while (!objekte.empty())
{
Base p = objekte.back();
objekte.pop_back();
delete p;
} //end of while
Das sieht erst einmal einfach aus, und bei kurzen Programmen ist das auch noch übersichtlich genug, um das Freigeben der Objekte nicht zu vergessen. Natürlich wird das deutlich komplizierter, wenn man den std::vector mit den Zeigern an mehreren Stellen nutzt und nicht einfach nur starr zwei Objekte erzeugt und in den std::vector schmeißt, sondern wenn man das dynamisch erledigt und ggf. mit hunderten von Objekten, bei denen nicht von vornherein klar ist, wie lange sie im std::vector verweilen werden. Daher rate ich dir hier zur Nutzung von sogenannten Smart Pointern. Diese sind seit C++11 verfügbar und kümmern sich selbst darum, dass das von ihnen verwaltete Objekt wieder freigegeben wird. Eine Möglichkeit wäre in deinem Fall also std::unique_ptr. Hier mal ein Beispiel mit std::unique_ptr. Der Code für die Klassen ist im Wesentlichen nur dazu da, um zu sehen, wann welche Methode aufgerufen wird:
Code:
#include <iostream>
#include <memory>
#include <vector>
class Base
{
public:
Base()
{
std::cout << "creating instance of Base" << std::endl;
}
virtual ~Base()
{
std::cout << "destroying instance of Base" << std::endl;
}
virtual void hello()
{
std::cout << "Hello from Base." << std::endl;
}
};
class ChildA : public Base
{
public:
ChildA()
{
std::cout << "creating instance of ChildA" << std::endl;
}
virtual ~ChildA()
{
std::cout << "destroying instance of ChildA" << std::endl;
}
virtual void hello()
{
std::cout << "Hello from ChildA." << std::endl;
}
};
class ChildB : public Base
{
public:
ChildB()
{
std::cout << "creating instance of ChildB" << std::endl;
}
virtual ~ChildB()
{
std::cout << "destroying instance of ChildB" << std::endl;
}
virtual void hello()
{
std::cout << "Hello from ChildB." << std::endl;
}
};
/* C++11 contains no make_unique(), but C++14 does, so we create our own
make_unique as workaround. */
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args&& ...args)
{
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
int main()
{
std::vector<std::unique_ptr<Base> > objects;
objects.push_back(make_unique<ChildA>());
objects.push_back(make_unique<ChildB>());
for(auto & i : objects)
{
i->hello();
}
return 0;
}
Ein explizites delete braucht's in diesem Falle nicht, das macht std::unique_ptr von allein. Die Ausgabe des Programms sieht dann so aus:
Code:
creating instance of Base
creating instance of ChildA
creating instance of Base
creating instance of ChildB
Hello from ChildA.
Hello from ChildB.
destroying instance of ChildA
destroying instance of Base
destroying instance of ChildB
destroying instance of Base
Wichtigster Punkt (oder einer der wichtigsten Punkte) dabei ist, dass der Destruktor von Base virtuell ist, damit auch die Destruktoren der abgeleiteten Klassen aufgerufen werden, wenn diese zerstört/freigegeben werden. Im obigen Beispiel ist das nicht so wichtig, aber wenn die Destruktoren der abgeleiteten Klassen irgendwann mal Ressourcen freigeben (z. B. Dateihandles schließen o. ä.), dann wird das ohne virtual ~Base() ein Problem, denn dann sähe die Ausgabe so aus:
Code:
creating instance of Base
creating instance of ChildA
creating instance of Base
creating instance of ChildB
Hello from ChildA.
Hello from ChildB.
destroying instance of Base
destroying instance of Base
Wie man sieht, werden die Destruktoren von ChildA und ChildB in dem Falle nicht aufgerufen, was natürlich nicht das gewünschte Verhalten ist.