[PL] FAQ: C++ funkcje wariadyczne (va_arg, etc) (last update: 2013-06-02, created: 2013-05-14) back to the list ↑
[FAQ - pytania (które dostaje via e-mail/IRC/jabber/etc, oraz na które natrafiam na forach) + moje odpowiedzi]

Poniżej moja odpowiedź na e-mail z prośbą o wyjaśnienie funkcji wariadycznych (variadic) w C++ (to te z ... w prototypie - z nieokreśloną liczbą parametrów opcjonalnych, np. printf).

=== email ===
...

W sumie funkcje wariadyczne są z jednej strony bardzo proste, a z drugiej jest trochę zabawy w C/C++ żeby taką sensownie napisać.

OK, w skrócie - funkcja wariadyczna to funkcja której można podać nie-stałą liczbę parametrów (tj. raz można taką wywołać z 3ma argumentami, raz z 5cioma, itd).
Zapewne kojarzysz funkcję printf - jak wiesz printf przyjmuje od jednego parametru (musi przyjmować jeden), do w zasadzie nieograniczonej ich liczby. Np.


printf("jakis tekst\n");
printf("to jest szesc -> %d\n", 6);
printf("%s %s %s\n", "ala", "ma", "kota");


Jeśli chodzi o konstrukcje takowych funkcji, to jest to też w miarę proste, przy czym jest jedna spora różnica w stosunku do normalnych funkcji - do "opcjonalnych" parametrów nie da się bezpośrednio (po nazwie) odwołać - zamiast tego trzeba korzystać z zestawu makr.
Żeby było zabawniej, z poziomu funkcji nie wiadomo ile tych opcjonalnych funkcji się dostało (nie jest to nigdzie implicit przekazywane). Więc trzeba sobie to samemu przekazać, implicit bądź explicit.
Zauważ, że np. w printf ilość opcjonalnych parametrów można wywnioskować "licząc" ilość %cośtam w stringu z pierwszego parametru.
Alternatywnie można we własnej funkcji zdeklarować ilość parametrów opcjonalnych w pierwszym parametrze.

Trochę przykładowego kodu - funkcja sumująca podane liczby (w pierwszym parametrze jest ilość liczb, potem są kolejne liczby jako int'y).
(do samych opcjonalnych parametrów odwołujesz się zestawem makr z <cstdarg>: va_start, va_end, va_arg; tu jest dobry opis: http://www.cplusplus.com/reference/cstdarg/)


#include <cstdio>

// wg standardu C++ nalezy uzywac tego naglowka jesli chce
// sie pracowac z va_start & co.
#include <cstdarg>

int sumuj(int n, ...) {
  // Zmienna pomocnicza typu va_list - za jej pomoca sie
  // odwolujesz to opcjonalnych parametrow.
  va_list parametry;

  // Inicjuje sie ta zmienna za pomoca makra va_start.
  // W drugim parametrze podaje sie ostatnia nie-opcjonalna
  // zmienna (w skrocie, ten kod mowi: "parametry beda
  // opisywac wszystkie zmienne po "n").
  va_start(parametry, n);

  // Teraz mozna z tych zmiennych korzystac.
  int suma = 0;
  for(int i = 0; i < n; i++) {
    // Dodajemy do "sumy" kolejne wartosci opcjonalnych parametrow.
    // Problem jest taki, ze nie jest explicit powiedziane w 
    // prototypie funkcji jakie one maja miec typy, wiec musimy
    // ta informacje tutaj przemycic (drugi parametr va_arg).
    // Samo va_arg zwraca wartosc opcjonalnego parametru i 
    // przeskakuje na nastepny (czyli przy kolejnym wywolaniu
    // zwroci kolejny parametr).
    suma += va_arg(parametry, int);
  }

  // I na koncu wypada po sobie posprzatac.
  va_end(parametry);

  // Koniec. Zwracamy wynik.
  return suma;
}

int main() {
  printf("Suma pustego zbioru: %d\n", sumuj(0));
  printf("Suma 1-5           : %d\n", sumuj(5, 1, 2, 3, 4, 5));
  printf("Suma 5, 7, 123, -2 : %d\n", sumuj(4, 5, 7, 123, -2));

  return 0;
}


Jeśli chodzi o problemy z powyższym, to jest kilka:
1. Pamiętaj, że nie masz określonej ilości parametrów opcjonalnych, a więc trzeba przekazać ich ilość explicit, albo chociaż implicit jak w printf jest.
2. Niektóre typy zachowują się "dziwnie" jeśli je przekażesz jako opcjonalny argument. Przykładowo, typ float jest zmieniany na double (a więc w va_arg musisz pamiętać, żeby zawsze używać double jako drugi parametr, nawet jeśli przekazałaś float'a).
3. Ponieważ va_arg nie zna typu, i ten tym mu explicit w funkcji "hardkodujesz" (ustawiasz na stałe), to musisz dbać o to, żeby przy wywołaniu danej funkcji zawsze wywoływać ją z dobrym typem parametrów. Np. funkcji sumuj() wyżej nie możesz podać floatów czy double, czy stringów - zwróci bzdury w takim wypadku i nie zadziała poprawnie.
(zauważ, że jeśli chodzi o printf to literka po % mówi jaki jest typ kolejnego parametru, więc w printf wewnętrznie jest switch/case który dla różnych literek wywołuje va_arg z różnymi typami)
4. Te "trzy kropki" (oznaczające opcjonalne parametry) w prototypie muszą być ostatnim argumentem. Nie można stworzyć funkcji w której parametry opcjonalne są w środku (w końcu skoro nie wiadomo ile ich jest, to trudno kompilatorowi wygenerować kod który odwołuje się do czegoś co jest po nich).
5. W C++11 (najnowszy standard C++) są jeszcze inne metody, żeby osiągnąć tego typu efekt (np. std::initializer_list).

...
=== koniec email ===

Oczywiście powyższe jest w pewnym uproszczeniu (tj. bez wnikania w wew. działania tego mechanizmu).
【 design & art by Xa / Gynvael Coldwind 】 【 logo font (birdman regular) by utopiafonts / Dale Harris 】