Trennung zwischen Speicherverwaltung und Auf- und Abbau von Objekten
Content |
Unsere letzte Lösung hatte den Nachteil, dass zu den Template-Abhängigkeiten ein default constructor für den Element-Typ gehörte und es unvermeidlich war, dass alle Objekte zunächst ohne Parameter konstruiert worden sind, bevor sie mit sinnvollem Inhalt gefüllt wurden.
Prinzipiell ist es möglich, das Belegen und Freigeben von Speicher zu trennen von dem Konstruieren und Abbauen eines Objekts.
Angenommen, T ist der Elementtyp und size die Zahl der gewünschten Elemente, dann können die vier genannten Operationen wie folgt durchgeführt werden:
-
Belegen von Speicher:
static_cast<T*>(operator new(sizeof(T) * size))
Mit operator new wird Speicher belegt, ohne das der Speicher irgendwie befüllt wird, es werden also keine Objekte konstruiert. Hier ist die Größe der gewünschten Speicherfläche in Bytes anzugeben, weswegen sizeof(T) mit size multipliziert werden muss, um die Gesamtgröße korrekt zu ermitteln. Normalerweise läuft dies auf std::malloc hinaus, aber anders als std::malloc kann operator new bei Bedarf überdefiniert werden. Wie std::malloc liefert auch der operator new einen Zeiger des Typs void* zurück, der mit Hilfe des static_cast-Operators in den passenden Zeigertyp konvertiert werden muss.
-
Konstruktion eines Objekts auf bereits belegtem Speicher:
// Annahme: data ist vom Typ T* new (data + index) T(other.data[index]);
In dieser Form des new-Operators wird in Klammern zuerst die Adresse angegeben, wo ein Objekt zu konstruieren ist. Danach folgt der Datentyp T und der oder die Parameter eines Konstruktors von T, wobei hier ein Kopierkonstruktor zum Einsatz gelangt.
-
Abbau eines Objekts, ohne den zugehörigen Speicherplatz freizugeben:
data[index].~T();
Der destructor kann explizit aufgerufen werden. In diesem Fall wird das Objekt nur abgebaut, ohne den zugehörigen Speicher freizugeben.
-
Freigabe von Speicher:
operator delete(data)
Aufgaben
-
Passen Sie Ihre Array-Template-Klasse dahingehend an, dass das Belegen und Freigaben des Speichers getrennt wird von dem Auf- und Abbau der Elemente. Der Kopierkonstruktor sollte entsprechend alle Elemente kopierkonstruieren können, ohne den default constructor zu verwenden. Der reguläre Konstruktor darf aber weiterhin die einzelnen Elemente mit dem default constructor konstruieren.
Testen Sie diese Fassung mit dem bereits existierenden Testprogramm unter Einsatz von valgrind.
-
Fügen Sie einen Konstruktor hinzu, der neben der Dimension noch eine const-lvalue-Referenz des Typs T erhält. Dieses Element dient dann als Kopiervorlage für alle im Array anzulegenden Elemente. Auf diese Weise ist die Anlage eines Arrays auch ohne einen default constructor für den Elementtyp möglich.
Testen Sie den neuen Konstruktor mit einem Elementtyp ohne default constructor. Sie können hierzu test4.cpp verwenden und müssten es entsprechend anpassen:
#include "array.hpp" struct Test { Test(int i) : i(i) { } int i; }; int main() { Array<Test> t(10); }
Wie Sie daran erkennen können, ist es ein Vorteil, dass nicht sämtliche Template-Abhängigkeiten für alle Methoden einer Klasse erfüllt sein müssen, solange wir mit den Abhängigkeiten auskommen der Methoden, die wir tatsächlich benötigen.