varargs vs. arrays

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:

1void foo(void* obj_ptr, ...);
2...
3foo(obj_ptr, NOW_COMES_A, a, NOW_COMES_B, b, NOW_COMES_C, c, END_MARK);

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.