Slicing

Unter Slicing versteht man ein Problem, dass bei CallByValue-Übergaben erhalten kann. Soll eine Klasseninstanz vollständig kopiert werden, so wird der Copy-Construktor aufgerufen. Im Normalfall ist dieser nicht implementiert, so dass byteweise kopiert wird.

class Anhaenger
{
public: 
  int SchraubenFuerReifen, SchraubenFuerGestell;
 
  Anhaenger( int p1, int p2 )
    : SchraubenFuerReifen( p1 ), SchraubenFuerGestell( p2 )
  { }
 
  virtual int SchraubenGesamt()
  {
    return SchraubenFuerReifen+SchraubenFuerGestell;
  }
};
 
class Wohnwagen : public Anhaenger
{
public:
  int SchraubenFuerAufbau;
 
  Wohnwagen( int p1, int p2, int p3 )
    : Anhaenger( p1, p2 ), SchraubenFuerAufbau( p3 )
  {}
 
  virtual int SchraubenGesamt()
  {
    return Anhaenger::SchraubenGesamt()+SchraubenFuerAufbau;
  }
};
 
int main( void )
{
  Wohnwagen w( 1, 2, 3 );
  Anhaenger a = w;
 
  printf( "Wohnwagen: %d\n", w.SchraubenGesamt() );
  printf( "Anhänger : %d\n", a.SchraubenGesamt() );
}

Der Anhänger a benötigt nun weniger Schrauben, da er den Aufbau nicht kennt. Für die Instanz a wird lediglich der Teil der Klasse Anhänger kopiert.

Ambassador:slicing xin$ ./a.out 
Wohnwagen: 6
Anhänger : 3

Slicing bei Call-By-Value Parameterübergaben

Wird eine abgeleitete Klasseninstanz an eine Funktion übergeben, die die Basisklasse erwartet, so kann nicht die komplette Klasse auf den Stack kopiert werden, sondern nur der Teil, der zur Basisklasse gehört. Da ein neues Objekt angelegt wird, entspricht der Datentyp nun auch der Basisklasse.

Schauen wir uns das ganze nun im Quelltext an:

#include <stdio.h>

// ...Klassendefinitionen wie oben...
 
int slicing( Anhaenger anhaenger )      // Hier wird ein neues Objekt angelegt
{
  return anhaenger.SchraubenGesamt();
}

int reference( Anhaenger & anhaenger )  // Hier wird kein neues Objekt angelegt
{
  return anhaenger.SchraubenGesamt();
}
 
int main()
{
  Wohnwagen w( 1, 2, 3 );

  printf( "Schrauben    direkt: %d\n", w.SchraubenGesamt() );
 
  int result = slicing( w );        // hier passiert ein Slicing.
  printf( "Schrauben   slicing: %d\n", result );
  
  result = reference( w );          // hier passiert KEIN Slicing.
  printf( "Schrauben reference: %d\n", result );
}

Wird dieses Programm ausgeführt, so entsteht folgende Ausgabe:

Ambassador:slicing xin$ g++ slicing.cpp 
Ambassador:slicing xin$ ./a.out 
Schrauben    direkt: 6
Schrauben   slicing: 3
Schrauben reference: 6

Üblicherweise wird Slicing ungewollt verwendet, daher wird es hier vorgestellt, üblicherweise sind Referenzen gewünscht.

Allerdings kann das Ergebnis von Slicing auch gewünscht sein, dass man wirklich die Funktion von Anhänger rufen möchte, die zu Anhaenger gehört und nicht die Funktion, die zu Wohnwagen gehört, weil man wirklich die Anzahl der Schrauben für den Anhänger benötigt.

In dem Fall sollte Slicing dennoch vermieden werden, da hier ein Objekt erzeugt und zerstört werden muss. Weiterhin funktioniert das ganze nicht, sobald die Basisklasse pure-virtual-Methoden enthält.

Das ganze lässt sich mit bereits bekannten Methoden verbessern. Damit das Ursprungsobjekt nicht kopiert werden muss, übergibt man die Referenz und ruft die Funktion der gewünschten Klasse:

int reference( Anhaenger & anhaenger )  // Hier wird kein neues Objekt angelegt
{
  return anhaenger.Anhaenger::SchraubenGesamt();
}

Nun gibt auch die Referenz den gewünschten Wert aus, ohne dass ein Objekt kopiert werden musste:

Schrauben    direkt: 6
Schrauben   slicing: 3
Schrauben reference: 3

Darf die Referenz nicht verändert werden, kann der Aufruf unter Überwachung der Const-Correctness geschehen.