Variadic functions
• tech • 551 słów • 3 minuty czytania
Dziś kilka slow o funkcjach z zmienna liczba argumentów, czyli Variadic Functions ;)
W ostatniej notce o makrach z zmienną listą argumentów wspomniałem o najpopularniejszej funkcji przyjmującej nieokreśloną liczbę argumentów - printf. Używanie takich funkcji jest bardzo proste, stworzenie również nie jest jakąś trudną i skomplikowaną sprawą.
Niezbędne do tego celu typy i makra umożliwiające dostep i operacje na niewyspecyfikowanych argumentach funkcji zadeklarowano w pliku nagłówkowym stdarg.h.
cppva_list
Typ va_list
jest obiektu listy, która zawiera wszystkie argumenty z jakimi została wywołana funkcja.
cppvoid va_start(va_list ap, last);
Makro va_start
musi być wywołane jako pierwsze przed dalszą “zabawą” przez makra va_arg
i va_end
. Inicjuje ona listę argumentów ap
do wykorzystania przez w/w makra. Parametr last
jest nazwą ostatniego znanego argumentu formalnego przed zmienna lista argumentów z jakimi została wywołana nasza funkcja.
cpptype va_arg(va_list ap, type);
Makro va_arg
służy podobierania kolejnych nie nazwanych argumentów z listy ap
zainicjowanej przez makro va_start
. Parametr type
jest nazwą typu pobieranego argumentu.
Każde wywołanie va_arg
modyfikuje listę ap
tak, że kolejne wywołanie zwraca kolejny argument z listy.
cppvoid va_end(va_list ap);
Makro va_end
kończy “zabawę” na liście argumentów ap
.
Każdemu wywołaniu va_start
i va_copy
musi odpowiadać wywołanie tego makra w obrębię tej samej funkcji.
cppvoid va_copy(va_list dest, va_list src);
Makro va_copy
kopiuje listę argumentów z src
do dest
. Jest ono potrzebne ponieważ typowe przypisanie z wykorzystaniem operatora =
może powodować problemy w różnych implementacjach i systemach.
Makra va_start
, va_arg
, i va_end
są zgodne z definicja standardu C89, a va_copy
wprowadzono w C99.
Jako przykład prosta funkcja wyliczającą wartość średnią podanych argumentów, jako ostatni parametr spodziewamy się wartości 0, aby dowiedzieć się kiedy kończy się lista argumentów. Przez to pozostałe argumenty (prócz pierwszego) nie powinny zawierać tej wartości.
double average(int first, ...) {
std::va_list ap;
va_start(ap, first);
int count = 1;
for (;; count++) {
int arg = va_arg(ap, int);
if (arg == 0)
break;
first += arg;
}
va_end(ap);
return first / static_cast<double>(count);
}
Na koniec prosta funkcja podobna do sprintf
z standardowej biblioteki C, ale zwracającą std::string
. Jest to tylko przykład jak łatwo można wykorzystać dostępne funkcje przyjmujące jako parametr obiekt va_list
.
#include <string>
#include <cstdio>
#include <cstdarg>
std::string Sprintf(const char* fmt, ...) {
size_t size = 256;
std::string str;
std::va_list ap;
while (1) {
str.reserve(size);
va_start(ap, fmt);
int ret = std::vsnprintf(const_cast<char*>(str.data()), size, fmt, ap);
str[size-1] = '\0';
va_end(ap);
if (ret == -1) {
str.clear();
break;
} else if (static_cast<size_t>(ret) >= size) {
size = ret + 1;
} else {
break;
}
}
return str;
}
Nic trudnego ;)
W C++ raczej nie zaleca się używania funkcji z zmienna liczbą argumentów, jeśli naprawdę nie jest to potrzebne. Wielokropek konieczny jest tylko w sytuacjach kiedy zmienia się liczba i typy argumentów. W innych przypadkach lepiej rozważyć wykorzystanie przeciążenia i domyślnych wartości funkcji, niż wyłączać kontrolę typów. Na dodatek nie można przekazywać w nie wyspecyfikowanych argumentach innych typów niż POD.
[dodano 20:25]
Inna użyteczną funkcją może być funkcja zwracającą min/max jak wersje STL-owe, ale ze zmienną liczbą argumentów:
template<typename T>
inline T max(T a, ...) {
std::va_list ap;
va_start(ap, a);
T max = a;
for (;;) {
T arg = static_cast<T>(va_arg(ap, T));
if (arg == 0)
break;
max = std::max(max, arg);
}
va_end(ap);
return max;
}
Jest to szablonowa funkcja z zmienna liczbą argumentów, nie mylić z szablonami z zamienną liczbą argumentów, o nich może wkrótce cos wspomnę ;)
Komentarze (0)