Перегрузка функции, когда параметры отличаются только эллипсами - PullRequest
9 голосов
/ 02 сентября 2010

У меня есть эта система регистрации, для которой я ищу ярлык для некоторых манипуляций со строками.

Система ведения журнала используется с помощью функциональных макросов, которые затем перенаправляют на один вызов функции. Например. #define Warning(...) LogMessage(eWarning, __VA_ARGS__);.

LogMessage затем делает snprintf в новый буфер и затем представляет это сообщение любым установленным целям журнала; printf, OutputDebugString и т. д.

К сожалению, я столкнулся с проблемой, когда имеющийся у нас буфер недостаточно велик, поэтому выходные данные усекаются. Я также понял, что этот метод потерпит неудачу, если в выходном сообщении есть символы процента, поскольку snprintf попытается обработать va_args. Наконец, поскольку большинство наших сообщений журнала не используют va_args, глупо копировать строку просто для того, чтобы представить ее регистраторам.

Итак, учитывая мой прототип функции, могу ли я перегружаться в зависимости от наличия эллипсов? Другими словами, должен ли я предположить, что я могу сделать что-то вроде:

LogMessage(LogLevel, const char* message, ...);
LogMessage(LogLevel, const char* message);

Мои попытки Google не дали ничего особенно полезного (просто показывал мне, что эллипсы будут совпадать, если ничего не происходит, в отличие от моих требований, которые ничто соответствует), и мой первоначальный удар по реализации только что дал мне неоднозначная ошибка вызова функции.

С ошибкой я должен просто признать, что я не могу этого сделать, но мне интересно, просто ли я использую компилятор или, может быть, я делаю это неправильно. Я могу добиться аналогичного эффекта с

// edited version of what I really have to remove our local APIs,
// please excuse minor errors
const char* message = NULL;
char buffer[512];

va_list args;
va_start(args, format);

if(strcmp(format, "%s") == 0) {
    message = va_arg(args, const char*);
}
else if (strchr(format, '%') == NULL) {
    message = format;
}
else {
    vsnprintf(buffer, 512, format, args);
    message = buffer;
}

va_end(args);

... но это кажется расточительным в типичном случае, который можно узнать просто по количеству передаваемых параметров. Например. если эллипсы ничего не соответствуют, выберите другую функцию? Если это не работает, есть ли другой способ, который я могу попробовать, который не требует, чтобы пользователь определился с именем макроса, какая функция будет вызвана? Честно говоря, это не так уж и много об «растрате», когда я понял, что если кто-то случайно сказал Error("Buffer not 100% full"); в своем сообщении журнала и в результате получил «Buffer not 1007.732873e10ull».

Редактировать: Хотя на мой пример ответили "не делай этого", можно ли ответить на сам вопрос?

Ответы [ 4 ]

3 голосов
/ 02 сентября 2010

Я также понял, что этот метод потерпит неудачу, если в выходном сообщении есть символы процента, поскольку snprintf попытается обработать va_args.

Тогда звоните, будьте осторожны. Если ваша функция задокументирована так, что она принимает строки формата printf, то вызывающая сторона должна избегать любых знаков процента. На самом деле ваша задача не пытаться обрабатывать строки неверного формата.

Честно говоря, это не так уж и много об "отходах", когда я понял, что если кто-то случайно сказал Error("Buffer not 100% full"); в своем сообщении журнала и в результате получил "Buffer not 1007.732873e10ull".

Я думаю, вам лучше придерживаться идеала C ++. В методах Java обычно проверяют допустимые аргументы и генерируют исключения, когда передают недопустимые значения. В C ++ вы просто позволяете звонящим выстрелить себе в ногу. Лучше, чтобы они написали 100%%, чем прыгать через обручи, чтобы защитить их от обучения правильному вызову вашей функции.

2 голосов
/ 06 марта 2014

Меня вдохновил первоначальный ответ на этот вопрос, но я получил небольшое улучшение.

static void LogMessage(LogLevel level, const char* message);

template <typename T>
static void LogMessage(LogLevel level, const char* format, T t, ...)
{
    LogMessageVA(level, format, (va_list)&t);
}

static void LogMessageVA(LogLevel level, const char* format, va_list argptr);

Это работает без необходимости "предполагать", что второй аргумент - const char *.

2 голосов
/ 12 сентября 2012

В C ++ 11 вы можете использовать шаблоны с явной специализацией для случая с одним аргументом:

void bar(int a, ...) {
  // va_list stuff
}

template <typename... T>
void foo(int a, T... args) { // (1)
  bar(a, args...); // or do all the vararg stuff here directly
}

template <>
void foo(int a) {            // (2)
  printf("single\n");
}

Тогда:

//foo();    // compile error, as expected
foo(1);     // uses (2)
foo(2,1);   // uses (1)
foo(3,1,"asdf"); // uses (1)
...
1 голос
/ 03 сентября 2010

Хорошо, я думаю, что нашел решение вопроса.

Дело в том, что вы не можете перегрузить, основываясь только на том, есть ли параметры для эллипсов или нет. То есть у вас не может быть функций с сигнатурами, которые зависят только от наличия эллипсов.

Однако, можно сделать что-то наподобие того, что я просил, если я уроню параметр const char* из прототипа эллипсов. * 1010 Т.е. *

LogMessage(LogLevel, ...);
LogMessage(LogLevel, const char* message);

однозначно, но теперь вы боретесь с тем фактом, что вы должны предположить, что первым параметром является const char*, но вполне может и не быть. Принимая совет Джона Кугельмана, может быть, это нормально; Вы документируете параметры, которые разрешены, и пользователь остерегается. Функция без эллипсов будет вызываться, если есть только const char*, и функция эллипсов будет вызываться, если есть что-то еще, включая документированный const char*, за которым следует некоторое количество параметров.

К сожалению, похоже, что это степень возможного решения, которое позволяет вам передавать va_args дочерним функциям, в моем случае - vsnprintf.

Вероятно, нехорошо принимать мой собственный ответ, даже если он отвечает на поставленный вопрос.

...