Placement New

Wir wissen, um ein Objekt zu erstellen, muss man erst den Speicher anfordern, um das Objekt dort erstellen zu können. Dafür verwenden wir den new-Operator und geben den Datentyp und die Parameter für den Konstruktor an:

  class Point
  {
  public:
    unsigned int x;
    unsigned int y;
 
    Point() : x(0), y(0) {};
    Point( unsigned int xpos, unsigned int ypos ) : x(xpos), y(ypos) {}
    ...
  };
 
  Point * o = new Point( 1, 2 );

Doch was, wenn man bereits den Speicher hat, in dem man ein Objekt erzeugen will? Nehmen wir an, wir wollen ein Array von Objekten anlegen:

  Point points[10];
 
  points[0] = Point( 0, 0 );
  points[1] = Point( 1, 1 );
  ...

Dann bedeutet das, dass wir nun 10 mal den Standard-Konstruktor rufen, um 10 gültige „Point“-Instanzen anzulegen und diese anschließend wieder überschreiben. Das ist grundsätzlich kein Problem, wenn es aber darum geht, wirklich sehr schnelle Software zu schreiben, dann sollte man sich so etwas natürlich sparen.

Placement New verwenden

Placement-New verwendet man, um einem Speicherblock ein Objekt zu erzeugen. Hierfür übergibt man einfach den Speicherblock als Parameter an den New-Operator:

  unsigned char * memblock = new unsigned char( 10 * sizeof( Point ) );
 
  new( &memblock[0] ) Point( 0, 0 );
  new( &memblock[1] ) Point( 1, 1 );
  ...

Um einen entsprechenden Pointer zu erhalten benötigen wir nun reinterpret_cast:

  Point * points = reinterpret_cast< Point * >( memblock );

Wir haben mit dieser Methode einen zusätzlichen Vorteil: Die verwendete Klasse benötigt nicht zwangsläufig einen Standard-Konstruktor, da wir diesen ja niemals rufen.

Möchten wir nun das Array wieder auflösen, so müssen wir für jedes Objekt den Destruktor rufen, das Objekt selbst könnte ja ebenfalls Speicher freigeben wollen. Hierfür können wir nicht delete verwenden, denn da wir keinen Speicher angefordert haben, dürfen wir auch keinen freigeben. Daher rufen wir den Destruktor selbst und löschen anschließend den Speicherblock.

  for( unsigned int i=0; i<10; i++ )
    points[i]::~Point();
 
  delete [] memblock;

Dies lässt sich nun bereits in einer Klasse kapseln, so dass man sich eine eigene Array-Klasse aufbauen kann, die mit wenig Overhead sehr schnell zu erzeugen ist.

Stolperfalle

Grundsätzlich muss der ein Destruktor nicht den Destruktor seiner Basisklasse rufen. Das ganze ist durchaus als „Hacking“ zu verstehen. Das Ganze sollte also durchaus mal getestet werden, ob der eigene Compiler hier mitspielt.

Überladen von Placement New

FIXME

Placement New und Microsoft Foundation Classes (MFC)

MFC definiert new per Makro um, so dass jeder Aufruf von „new“ mit Dateiname und Zeilennummer versehen wird. Das ist praktisch, wenn man Speicherlecks sucht und prüfen möchte, wo der Speicher angefordert wird. Leider wurde hier keine Rücksicht auf Placement new genommen. Es kommt folgender Fehler:

error C2061: Syntaxfehler: Bezeichner 'ptr'

Die Speicherüberwachung macht bei Placement-New keinen Sinn, man kann das Problem damit umgehen, dass man die Funktionen, die Placement-New verwenden in eine eigene .cpp-Datei auslagert und zunächst das MFC-Macro deaktiviert:

#undef new

Wie gesagt - hier wird ein MFC-Makro-deaktiviert und nicht der new-Operator. Durch die Deaktivierung des Makros wird der echte new-Operator wieder zugänglich und Placement-New kann wieder verwendet werden.