varargs vs. arrays
« | 11 Mar 2020 | »Nachdem ich im GATE Projekt die C-Delegate-Lösung mit varargs umgesetzt hatte, war ich glücklich diese Methode einmal “zielführend” wo eingesetzt zu haben.
Wenn man aber reguläre APIs mit varargs
überfüttert, schafft man sich damit
aber mehr Probleme als Vorteile.
Und das kann man am Beispiel GTK+
gut nachvollziehen.
Dass man beliebig viele Argumente mit beliebig unterschiedlichen Typen an eine Funktion übergeben kann, hört sich auf den ersten Blick verlockend an.
Eine Funkion kann damit unterschiedlichste Aufrufszenarien abdecken.
Bestes Beispiel:
1printf(format, ...);
Leider hat dieses Vorgehen aber einen großen Nachteil: Es ist “hard-coded” und damit nicht zur Laufzeit abänderbar.
Ob printf()
jetzt einen char*
oder einen int
abhandelt, wird fest in
Code gegossen, und man kann zur Laufzeit die Aufrufparameter nicht abändern.
Da müsste man schon für jeden erdenklichen Fall per if
eine Verzweigung
schaffen.
Für solche Fälle nutzt man daher besser keine varargs
, sondern
Arrays von variablen Typen, wie z.B.: Unions, boost::any
oder void*
.
GTK+ nutzt z.B.: bei seinen Methoden für das Model-Controller-View Schema
einige APIs mit varargs
.
Hier kommen oft Parameter-Gruppen zum Einsatz die folgendes Schema umsetzen:
Solche NOW_COMES_*
Identifier sind meist Integer die man leicht durch Variablen
ersetzen kann. Doch die Typen der eigentlichen Argumente sind feststehend.
Folgt das Ganze aber dem Schema:
1struct my_param 2{ 3 int param_type_id; 4 union 5 { 6 type_a_t param_type_a; 7 type_b_t param_type_b; 8 type_c_t param_type_c; 9 type_d_t param_type_d; 10 } 11}; 12 13void foo(void* obj_ptr, struct my_param* params, size_t param_count); 14 15... 16 17struct my_param params[3]; 18... 19foo(obj_ptr, params, sizeof(params) / sizeof(params[0]));
dann können die Parameter zur Laufzeit “frei” zusammengestellt werden. Man befüllt das Array wie man will in Sachen Anzahl und Typen.
Und das ist ein wichtige Erkenntnis. Während varargs
auf den ersten Blick
einfach und elegant wirken, fehlt ihnen ein großes Maß an Flexibilität beim
dynamischen Zusammenstellen von Parametern.
Bei der Arbeit mit GTK+ ist mir dieses Schema wieder untergekommen, nämlich
beim Erstellen von Store Objekten für das
Model-View-Controller
Konstrukt bei Listviews.
Hier wäre das Befüllen des Models mit vararg
Funktionen problematisch, weil
zur Entwurfszeit vielleicht noch gar nicht feststeht, wie viele Spalten die
Listview anzeigen soll.
Fazit
Die Codebeispiele für die GTK Listview hatten mich befürchten lassen, dass
alles über vararg
Funktionen gemacht werden muss.
Zum Glück fand ich in der Doku schnell auch alternative Funktionen um das
Listview-Modell zu manipulieren.
Offenbar waren sowohl die Vorteile wie auch die Nachteile von varargs
auch den GTK+ Leuten bekannt und sie haben deshalb beide Varianten
implementiert.
Auch im GATE Projekt werden für Datenausgaben in Strings und Stream
vararg
-Print-Routinen benutzt. Aber auch hier sind diese nur vereinfachende
Weiterleitungen auf Funktionen mit festen Argumenten.
Irgendwie schön zu sehen, dass viele Entwickler in unterschiedlichen Projekten ohne sich zu kennen immer wieder auf die gleichen Ergebnisse kommen, wenn es um Schnittstellendefinition geht.