Несколько замечаний до ...
Переменные аргументы добавляют что-то важное для C. (Без них семейство printf()
вряд ли можно было бы реализовать в чистом C.)основан на макросах, которые сокращают разрыв между уникальным кодом и различными реализациями, которые очень зависят от платформы и оборудования.В C ++ использование таких макросов должно быть максимально предотвращено в целом.template
обеспечивают безопасную альтернативу типа.
Учитывая, что заменой C ++ для семейства printf()
являются потоковые классы с множеством перегруженных операторов сдвига, IMHO, в определенныхВ ситуациях принцип со строкой форматирования и переменным числом аргументов является гораздо более подходящим (например, для вывода уведомлений или сообщений журнала во время выполнения, где дополнительно должна применяться локализация, которая может даже изменить порядок вывода форматированных аргументов).Потоковые классы с операторами сдвига не могут быть использованы для этого.Несколько лет назад, когда мы использовали gtkmm
, я нашел Glib::ustring::compose()
очень подходящим для этой работы.Тем не менее, он (как и printf()
) не "безопасен во время компиляции", это по крайней мере безопасный тип, и проверки во время выполнения могут быть легко применены.И: AFAIK, он не содержит макросов - реализован только в чистом коде C ++.
Однако, имея в виду приведенный выше отказ от ответственности, я попытался понять / решить проблему OP, касающуюся реализации с помощьюпеременные аргументы.При написании примера кода я просто понял, почему становится проблемой вызвать конструктор, принимающий va_list
из другого конструктора.Следуя схеме, которая хорошо представлена в ответе SO: Передача аргументов переменной в другую функцию, которая принимает список аргументов переменной , создает следующий код:
struct s0 {
s0(const char *fmt, va_list args);
};
struct s1: s0 {
s1(const char *fmt, ...):
va_list args;
va_start(args, fmt);
s0(fmt, args)
va_end(args);
{ }
};
Ой, неправильно !Я действительно хотел бы знать, есть ли какая-либо комбинация платформы / оборудования, где это успешно скомпилируется.Итак, вызов конструктора базового класса должен быть перенесен в тело:
struct s1: s0 {
s1(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
s0(fmt, args);
va_end(args);
}
};
Ой, неправильно снова!Помимо того, что я даже не знаю, может ли конструктор вызываться в теле функции, я не могу предотвратить неявный вызов конструктора по умолчанию в этом случае, который недоступен.
Итак, яВ итоге мы убедились, что конструктор с va_list
является , а не решением в этом случае.Я не вижу другого способа отделить функцию, которая получает / обрабатывает va_list
:
#include <iostream>
#include <cstdio>
#include <cstdarg>
struct s0 {
char name[64];
s0 *parent;
int stackLevel;
s0(s0 *parent, const char *nameMask, ...):
parent(parent)
{
va_list args;
va_start(args, nameMask);
vsnprintf(name, sizeof name, nameMask, args);
va_end(args);
}
~s0() = default;
protected:
// constructor for derived classes
s0(s0 *parent): name(""), parent(parent) { }
// construction of name
void initName(const char *nameMask, va_list args)
{
vsnprintf(name, sizeof name, nameMask, args);
}
};
struct s1: s0 {
int p1;
s1(s0 *parent, int p1, const char *nameMask, ...):
s0(parent), p1(p1)
{
va_list args;
va_start(args, nameMask);
initName(nameMask, args);
va_end(args);
}
~s1() = default;
};
int main(int argc, char *argv[])
{
s0 s0(nullptr, "s0[%d]", 0);
std::cout << "s0 '" << s0.name << "'\n";
s1 s1(&s0, 1, "s1[%d]", 1);
std::cout << "s1 '" << s1.name << "'\n";
return 0;
}
Вывод:
s0 's0[0]'
s1 's1[1]'
Демонстрация в реальном времени на coliru
Примечание:
При реализации примера я понял, что vsprintf_s()
является частью стандарта C11, ноне C ++ 11.Поэтому я заменил его на std::vsnprintf()
, который работает достаточно похоже.