Statisches Casten - static_cast

Ein static_cast wird wie folgt aufgebaut

static_cast< neuerTyp >( object )

Zurückgegeben wird ein Objekt im gewünschten neuerTyp.

static_cast eignet sich für die Umformung von Zahlentypen, Zeigern und Referenzen. Zwischen Klassen werden unäre Konstruktoren, also Konstruktoren, die als einzigen Parameter den zu konvertierenden Datentyp besitzen (auch explizite, siehe auch Casten durch Konstruieren) und Konvertierungsoperatoren der zu Klasse, in der das vorhandene Objekt vorliegt.

Soll ein ein Cast ohne Prüfung zur Laufzeit geschehen, so werden die Daten brachial umgebogen. Dabei kann es zu Datenverlusten kommen:

double pi = 3.1415;
int almostPi = pi;

Primitive

In diesem Fall würde der Nachkommateil abgeschnitten, es würden also Daten verloren gehen. Daher sollte der Compiler hier meckern. Der GCC tut dies:

warning: converting to `int' from `double'

Wenn der Entwickler sich jedoch sicher damit ist, was er da tut, also den Datenverlust berücksichtigt hat, so kann er diese Absicht durch einen static_cast dem Compiler mitteilen und der Compiler akzeptiert den Wunsch des Entwicklers. Das folgende Programm kompiliert ohne eine Warnung:

#include <stdio.h>
 
int main(int argc, char **argv)
{
  double pi = 3.1415;
  int almostPi = static_cast< int >( pi );
 
  return 0;
}

Gleiches gilt für beispielsweise eine Umwandlung von int (32 Bit breit) zu short (16 Bit). Auch hier können Daten verloren gehen und der Entwickler muss sich sicher sein, dass er dies berücksichtigt hat.

Objekte

Wie zuvor beschrieben, sucht static_cast nach Konvertierungsoperatoren oder konstruiert ein neues Objekt.

Versuchen wir folgendes:

#include "stdio.h"
 
// Die Existenz einer Klasse Point3d muss zur Deklaration
// von Point2d( Point3d & ) schonmal bekannt sein.
class Point3d;
class Point2d
{
  public:
    int x, y;
 
    Point2d( int ax, int ay ) : x( ax ), y( ay ) {}
 
    explicit Point2d( Point3d & p3 );
};
 
class Point3d 
{	
  public:
    int x, y, z;
 
    Point3d( int ax, int ay, int az ) : x( ax ), y( ay ), z( az ) {}
};
 
// Da wir alles in einen Code packen müssen Point2d erst bekannt sein,
// bevor wir darauf zugreifen können 
 
Point2d::Point2d( Point3d & p3 )
  : x( p3.x )
  , y( p3.y )
{ 
  printf( "Konstruiere Point2d\n" );
}
 
int main(int argc, char **argv)
{
  Point3d p3( 1, 2, 3 );
  Point2d p2( 4, 5 );
 
  p2 = p3;
 
  return 0;
}

Der GCC meldet:

error: no match for 'operator=' in 'p2 = p3'

da es keinen Zuweisungsoperator gibt. C++ darf auch nicht automatisch casten, da der unäre Konstruktor als explizit gekennzeichnet ist.

Wir können also jetzt den Konstruktor explizit aufrufen:

p2 = Point2d( p3 );

oder statisch casten:

p2 = static_cast< Point2d >( p3 );

static_cast wird sich damit für den Aufruf des Konstruktors entscheiden. Hier hilft static_cast also deutlich hervorzugeben, dass hier Daten verloren gehen (nämlich die z-Koordinate des Point3d).

Falls es eine Auswahl zwischen einem Konvertierungsoperatoren und einer Umformung durch Konstruktion gibt, so entscheidet sich static_cast für die Umformung durch Konstruktion, auch wenn der Konstruktor als explicit gekennzeichnet ist.

Zeiger und Referenzen

Basierend auf dem Programm im Abschnitt Objekte ändere ich hier die main-Funktion wie folgt:

int main(int argc, char **argv)
{
	Point3d p3( 1, 2, 3 );
	Point2d p2( 4, 5 );
 
        Point2d * pp2;
	Point3d * pp3 = &p3;
 
	pp2 = static_cast< Point2d * >( pp3 );   // Fehlermeldung
	pp2 = (Point2d *) pp3;                   // wird akzeptiert
 
	return 0;
}

Wir haben nun zwei zusätzliche Zeiger, wobei pp2 auf einen Point2d zeigt und pp3 auf einen Point3d. pp2 wird zweimal der Wert von pp3 zugewiesen, einmal mit einem Static-Cast und einmal mit einem C-Style-Cast. Der Static-Cast wirft dabei folgende Meldung:

error: invalid static_cast from type `Point3d*' to type `Point2d*'

Der Hinweis ist korrekt, denn Point3d und Point2d stehen in keinerlei Verbindung zueinander. Die Zeiger dürfen also nicht einfach ausgetauscht werden. Beim C-Style-Cast, wird der Zeigeraustausch akzeptiert nach dem Motto „Zeiger ist Zeiger“. static_cast liefert hier also den Hinweis, dass man gerade im Begriff ist, Mist zu bauen, während der C-Style-Cast hier jegliche Warnung unterdrückt.

Mit static_cast lassen sich Zeiger innerhalb der Objekthierarchie casten: Folgendes Beispiel:

class Tier
{
};
 
class Hund : public Tier
{
};
 
int main(int argc, char **argv)
{
	Hund * hund = new Hund();
	Tier * tier;
 
	tier = hund;

Da der Hund ein Tier ist, muss hier kein Cast durchgeführt werden, denn ein Hund verhält sich ja genauso, wie ein Tier. Aber im umgekehrten Fall, muss der Compiler die Anweisung hund = tier ablehnen, da eine Katze ja auch ein Tier wäre, und wenn das Tier eine Katze ist, so würde diese Anweisung ja bedeuten, dass der Zeiger auf den Hund in Wirklichkeit auf eine Katze zeigt. Das darf natürlich nicht sein.

Das letzte Wort in der Programmierung hat jedoch immer der Entwickler. Und da Hund und Tier in einer Hierarchie stehen, ist der expliziten Aussage, dass ein Tier definitiv ein Hund ist, nichts entgegen zu bringen. Wenn dem nicht so ist, stürzt das Programm eventuell ab, aber wenn der Entwickler sich sicher ist, so hat er nunmal das letzte Wort. Dies kann der Entwickler mit einem static_cast erzwingen:

	Hund * meinHund;
 
	meinHund = static_cast< Hund * >( tier );
 
	return 0;
}

Dieser Quelltext wird vom C++-Compiler so akzeptiert. Die Aussage lautet 'Ich, der Entwickler, weiß zu 100%, dass das Objekt, auf das tier zeigt, ein Hund ist.'.