Immer wieder sieht man Code wie diesen:
CString strText = _T("xyz");
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
strText, 4711);
Sieht harmlos aus und funktioniert. Warum eigentlich?
Es funktioniert nur aus einem einzigen Grund:
CString ist 4 Bytes groß, besteht nur aus einem einzigen Element und das ist ein Zeiger auf einen TCHAR Array!
Das ganze funktioniert sofort nicht mehr wenn wir eine eigene CMyString Klasse ableiten, der wir eine virtuelle Funktion zuordnen. Was passiert nun?
Nun ist CString nicht mehr 4 Bytes groß sondern 8 Bytes und besteht aus zwei Zeigern. Einem Zeiger auf eine vtable und einen Zeiger auf einen TCHAR Array.
Nicht nur wird jetzt kein Text mehr ausgegeben, sondern auch die Zahl wird nicht korrekt ausgegeben! Das ganze obwohl CMyString immer noch einen Umwandlungsoperator für LPCTSTR hat.
Das Problem ist aber, dass _tprintf eine Variable Anzahl von Argumenten (Ellipsis) hat und der C/C++ Compiler gar nicht weiß, was _tprintf erwartet. Also wird das ganze Objekt auf den Stack geschoben.
Man macht eine kleine Änderung, und schon geht die Sache in die Hose. Das habe ich ja schon in dem Artikel Die Cx2y Falle beschrieben. Oder sollte also so etwas wie eine Änderung der Implementierung von CString erfolgen würde solch ein Programm auch sofort nicht mehr funktionieren. Man muss jedoch vermuten, dass Microsoft sich das gar nicht erlauben würde, weil zu viele Entwickler solchen nicht portablen und unabhängigen Code verwenden.
Wie macht man es richtig? Man verwendet den entsprechenden cast oder eine Funktion, die den entsprechenden Typ returniert.
CString strText = _T("xyz");
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
static_cast<LPCTSTR>(strText), 4711);
// -- oder --
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
strText.GetString(), 4711);
Man sollte grundsätzlich bei der Verwendung von einer variablen Anzahl von Argumenten (Ellipsis) immer auf den Typ casten, der auch letzten Endes erwartet wird.