Arrays: Wenn C templates hätte...
« | 31 Oct 2018 | »… dann wäre C++ womöglich arbeitslos.
Naja, vielleicht nicht ganz, also Spaß bei Seite. Aber generische Programmierung mit Templates zählt für mich neben RAII zu den wichtigsten Features von C++.
Also Frage: Wie löst man dieses Manko in reinem C?
Die beiden üblichen und widerlichen Antworten sind:
Macros werden extrem lästig, wenn sie mehrere Codezeilen beinhalten,
denn kein mir bekannter Debugger schafft es korrekt in Macros
zu “springen”.
Seiteneffekte sind meist auch immer “vorprogrammiert”.
Und mit void*
schaltet man natürlich sehr effizient alle Hilfestellungen des
Compilers aus. Und dann kann aus einem char*
schnell ein int*
und
umgekehrt werden.
… aber wenn man Glück hat, terminiert einem das OS den Prozess noch bevor kritische Daten wegen Typenfehlern zerstört werden.
Auch die automatische Wahl von Konstruktoren, Kopierkonstruktoren und Destruktoren passend zum gewählten Typen ist in C++ ein riesiger Vorteil.
Ein Beispiel:
Wir wollen ein Array eines
beliebigen Typen in einem speziellen Speicherbereich aufbauen.
(Wir ignorieren der Einfachheit halber mal alle Formen von Fehlern und deren
Behandlung.)
In C++ ist das leicht:
1 2struct foo 3{ 4 int bar; 5 6 foo() 7 : bar(42) 8 { 9 } 10}; 11 12template<typename T> T* create_array_list(size_t count) 13{ 14 T* ret = (T*)special_fancy_malloc(sizeof(T) * count); 15 T* current = ret; 16 while(count-- != 0) 17 { 18 /* invoke constructor of T at the current address */ 19 new ((void*)current) T(); 20 ++current; 21 } 22 return ret; 23} 24 25int main() 26{ 27 int* special_buffer = create_array_list<foo>(12); 28 ... 29}
Und was machen wir in C? Naja, da müssen wir Konstruktoren erst mal generisch machen und für jeden Typen einen händisch schreiben.
1struct foo 2{ 3 int bar; 4}; 5 6 7typedef void* (*generic_constructor_t)(void* destaddress); 8 9void* create_array_list(size_t count, size_t type_length, 10 generic_constructor_t constructor_function) 11{ 12 void* ret = special_fancy_malloc(type_length * count); 13 char* current = (char*)ret; 14 while(count-- != 0) 15 { 16 if(constructor_function != NULL) 17 { 18 if(constructor_function(current) == NULL) 19 { 20 /* C++ would throw an exception */ 21 } 22 } 23 else 24 { 25 /* no constructor / plain old data 26 -> no initialization required */ 27 } 28 current += type_length; 29 } 30 return ret; 31} 32 33void* foo_constructor(void* address) 34{ 35 struct foo* foo_address = (struct foo*)address; 36 foo_address->bar = 42; 37 return address; 38} 39 40int main() 41{ 42 int* special_buffer = create_array_list( 43 12, sizeof(struct foo), &foo_constructor); 44 ... 45}
Dass wir mehr schreiben müssen, stört mich gar nicht mal so sehr.
Aber dass die Funktion create_array_list
immer die Konstruktor-Funktion als
Parameter benötigt, ist schon wesentlich ärgerlicher.
All diese Magie erledigt sonst der C++ Compiler für uns.
Wir können uns sogar die C-artigen Konstruktor-Funktion in C++ automatisch
generieren lassen.
Naja … man muss es eben so sehen: C ist einfach eine größere Herausforderung. Aber abseits von sportlichen Herausforderungen ist eines klar:
C++ rulez!