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)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/