I/O-Manipulatoren

Die Ausgabe von std::endl soll nicht nur einen Zeilenumbruch erzeugen, sondern auch noch ein flush() auf den Ausgabestrom aufrufen. Da ist es nicht so einfach mit einer Konstante getan. Hier greift das Konzept der I/O-Manipulatoren.

Video

Quelltext

#include <iostream>
#include <iomanip>

std::ostream &endline(std::ostream &os)
{
    os.put('\n');
    return os.flush();
}

std::ostream &endline_int(std::ostream &os, int)
{
    os.put('\n');
    return os.flush();
}

std::ostream &operator<<(std::ostream &os, std::ostream & (*manip)(std::ostream &, int))
{
    return manip(os, 0);
}

int main () {
    std::cout << "Ein Test-Text" << std::endl;
    std::cout << "Ein weiterer Text" << endline;
    std::cout << "Test-Text" << endline_int;
    return 0;
}

Erklärung

I/O-Manipulatoren wie std::endl oder std::hex sind in C++ nicht als einfache Konstanten implementiert, die der std::ostream auswertet und dann sein Verhalten ändert. Das würde die Sache nämlich nicht flexibel genug machen, da nur die Manipulatoren verstanden würden, die bei der Implementierung der Stream-Klasse bekannt waren. C++ geht daher einen anderen Weg: jeder I/O-Manipulator ist eine Funktion, die einer speziellen Signatur genügt (hier im Beispiel std::ostream &manip(std::ostream &). Es gibt noch andere, die aber ähnlich funktionieren.). Wird ein Funktionspointer auf eine dieser Funktionen ausgegeben, so greift ein spezieller Ausgabeoperator (vergleichbar zu dem in Zeile 16, allerdings ohne den int-Parameter in der Signatur. Erklärung für diesen Parameter im Video!), dessen Aufgabe es ist, den Manipulator aufzurufen. Als Parameter bekommt der Manipulator den std::ostream, auf dem er ausgegeben wird. Die Rückgabe des Manipulators wird wiederum von dem speziellen Ausgabeoperator zurückgeliefert (siehe Zeile 18). 

Auf diese Weise sind komplexeste Verhaltensweisen implementierbar, ohne auf eine Anpassung der ostream-Klasse bauen zu müssen. std::endl gibt beispielsweise nicht nur einen Zeilenumbruch auf, sondern ruft auf den Ausgabestrom die Methode flush() auf. Dadurch wird sichergestellt, dass die ggf. im Puffer des Stroms liegenden Daten wirklich ausgegeben werden (bspw. durch Übergabe an den Betriebssystemkern). Manipulatoren wie bspw. std::hex setzen ihrerseits Flags im Stream um (und erzeugen selbst überhaupt keine Ausgabe). Das Konzept ist insgesamt recht flexibel.