Zuweisungen

Beim letzten Mal haben wir gesehen, wie man mittels selbst implementiertem Copy-Konstruktor das Kopieren von Objekten verbieten kann. Diesmal soll das Beispiel zeigen, wie man das Kopierverhalten eines selbstdefinierten Typs anpassen kann.

Video

Quellcode

#include <stdexcept>
#include <iostream>

class IntBuffer
{
  unsigned int *mBuffer;
  unsigned int mSize;

  public:
    explicit IntBuffer(unsigned int i)
      : mBuffer(new unsigned int[i]), mSize(i)
    {
    }
    
    IntBuffer(IntBuffer const &other)
      : mBuffer(new unsigned int[other.mSize]), mSize(other.mSize)
    {
      for (unsigned int i = 0; i < mSize; ++i) {
	mBuffer[i] = other.mBuffer[i];
      }
    }
    
    ~IntBuffer()
    {
      delete[] mBuffer;
    }
    
    unsigned int &operator[](unsigned int position)
    {
      if (position < mSize) {
	return mBuffer[position];
      }
      throw std::runtime_error("Out of bounds");
    }
    
    unsigned int size()
    {
      return mSize;
    }
    
    IntBuffer &operator=(IntBuffer const &other)
    {
      delete[] mBuffer;
      mBuffer = new unsigned int[other.mSize];
      mSize = other.mSize;
      for (unsigned int i = 0; i < mSize; ++i) {
	mBuffer[i] = other.mBuffer[i];
      }
      return *this;
    }
};

void printBuffer(IntBuffer b)
{
  for (unsigned int i = 0; i < b.size(); ++i) {
    std::cout << b[i] << " ";
  }
  std::cout << std::endl;
}

int main()
{
  IntBuffer b(10);
  
  b[5] = 15;
  std::cout << "b: ";
  printBuffer(b);
  IntBuffer b2(b);
  std::cout << "b2: ";
  printBuffer(b2);
  IntBuffer b3(5);
  b3 = b;
  std::cout << "b3: ";
  printBuffer(b3);
}

Erklärung

Im Grunde genommen ähnlich wie beim letzten Mal: ein selbst definierter Copy-Konstruktor erlaubt einer Klasse, beliebiges Verhalten zu implementieren. Diesmal wird seine Benutzung allerdings nicht verboten, sondern er implementiert ein Deep-Copy-Verhalten, d.h. der interne Puffer eines IntBuffer-Objektes wird tatsächlich auch mit kopiert. Zu diesem Zweck wird in Zeile 21 in der Initialisierungsliste ein Puffer reserviert, der groß genug ist, um eine Kopie des Quellobjektes zu halten. Die Größe des Quellobjektes wird dann auch noch übernommen. Im Konstruktor selbst wird dann der Inhalt des Quellpuffers Stück für Stück kopiert. Soweit, so erwartbar.

Eine Neuigkeit zeigt sich im sogenannten Copy-Assignment-Operator in Zeile 41: der ist nämlich der zweite Weg, wie man ein Objekt in ein andere kopieren kann. Dabei existieren beide Objekte schon, aber der Inhalt des zugewiesenen Objektes wird mit dem des Quellobjektes überschrieben. Auch dieser Operator wird automatisch generiert, wenn man ihn nicht angibt, was uns mit unserem Pointer im Objekt natürlich wieder in Probleme bringt. Daher überschreiben wir den auch und implementieren ein Deep-Copy-Verhalten. Wichtig hierbei nur: da das Zielobjekt, dessen Copy-Assignment-Operator aufgerufen wird, ja schon existiert, hat das natürlich einen internen Puffer, den wir erst freigeben müssen (Zeile 43), um ein Speicherleck zu vermeiden.

Im Video wird noch die Rule of Three erwähnt. Die verbindet Copy-Konstruktor, Destruktor und Copy-Assignment-Operator miteinander. Als Daumenregel gilt: wenn man eins davon überschreibt, dann will man meist auch die anderen beiden überschreiben.