C ++, как использовать многоточие без предшествующего аргумента - PullRequest
2 голосов
/ 31 марта 2011

Привет, у меня есть класс с функцией-членом, которая принимает переменное число аргументов.Класс знает, сколько аргументов ожидать после его создания .например,

class myClass
{
 myClass(int num_args){na=num_args;};
 private:
 int na;
 public:
 void do_something(int num_args, ...);
}

void myClass::do_something(int num_args, ...)
{
 va_list vl;
 va_start(vl, num_args);
 for(int i=0; i < num_args; i++)
  do_anotherthing(va_arg(vl, type));
 va_end
}

, поэтому я в итоге звоню следующим образом:

myClass A(5);
myClass B(4);

A.do_something(5, a, b, c, d, e);
B.do_something(4, a, b, c, d);

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

void do_something(...);

и каким-то образом заставить stdargs работать с классом num_args, а не с передаваемым значением.

Большое спасибо затвои мысли.

Ответы [ 6 ]

2 голосов
/ 31 марта 2011

Пожалуйста, не делай этого. Переменное число параметров очень редко встречается в C ++

Лучший способ добиться того, чего вы хотите, - создать список int и передать этот список в качестве параметра

Вы можете проверить размер списка внутри функции

1 голос
/ 31 марта 2011

Вместо использования varargs вы можете использовать operator<< как стандартные потоки C ++?Тогда все это безопасно для типов, и вам не нужно беспокоиться о проблемах, которые вы отметили с помощью varargs.

1 голос
/ 31 марта 2011

Я не понимаю, почему вы не можете использовать na в самой функции, то есть

void myClass::do_something(int num_args, ...)
{
 va_list vl;
 va_start(vl, na);
 for(int i=0; i < na; i++)
   do_anotherthing(va_arg(vl, type));
 va_end
}

Но что такое type?Какой тип do_anotherthing ожидает?

К сожалению, переменные методы не являются безопасными по типу, и неясно, будет ли va_arg выдавать ошибку, если vl не тот тип, который вы укажете.Вместо этого попробуйте сделать что-то вроде boost::format, то есть

A % a % b % c % d % e;

, где operator% выполняет do_anotherthing для каждого поставляемого элемента, пока не будет достигнуто максимальное число.

Редактировать : После дальнейших размышлений, вероятно, будет лучше, если do_something вернет вспомогательный объект, который сделает все для него, например,

do_something_helper myClass::do_something() { 
  return do_something_helper( na ); }

struct do_something_helper {
  int count;
  do_something_helper( int c ) : count( c ) {}

  template< class T >
  do_something_helper& operator%( T val ) {
     --count;
     if ( count < 0 ) {
      //trigger some error condition
     }
     do_anotherthing( val );
     return *this;
  }

  ~do_something_helper() {
    if ( count > 0 ) { // too few args
      //trigger some error condition
    }
  }
}

, который будет использовать

A.do_something() % a % b % c % d % e;
0 голосов
/ 18 января 2014

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

Этот код работает для моего компилятора (VS2013), и я сожалею, что мне пришлось использовать встроенную сборку, но первая идея использовать локальную переменную стека не могла работать из-за организации стека.Итак, вот оно:

#include <iostream>
#include <cstdarg>
#include <stdint.h>

using namespace std;

double avg(double * sp, ...);

#define AVERAGE(n, ...)                         \
{                                               \
    int * stackP;                               \
    __asm {                                     \
        __asm mov ebx, esp                      \
        __asm mov stackP, ebx                   \
    }                                           \
    double a = avg(stackP, n, __VA_ARGS__);     \
    cout << "Average is: " << a << endl;        \
}

template <typename T>
double avg(int * sp, T n, ...)
{
    va_list list;
    va_start(list, n);
    double result = n;
    unsigned int count = 1;
    T * pArg = 0;

    while (sp > reinterpret_cast<int *>(pArg + 1))
    {
        pArg = &(va_arg(list, T));
        result += *pArg;
        count++;
    }

    va_end(list);

    result /= count;
    return result;
}

void main()
{
    AVERAGE(1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6);
    AVERAGE(1.2, 1.3, 1.4, 1.5, 1.6);
    AVERAGE(1.2, 1.3, 1.4);
    AVERAGE(2, 3, 4);
    AVERAGE(2, 3, 4, 5, 6);
}
0 голосов
/ 31 марта 2011

спасибо всем.меня не беспокоит, что класс безопасен, это очень маленький код, и он будет использоваться только в строго контролируемых условиях.я не хочу использовать оператор (<< например), так как я хочу, чтобы один вызов функции принимал все параметры.То же самое касается настройки вектора и передачи этого, я хочу сделать все это за один вызов (аргументы всегда являются строками).Я просто продолжу указывать количество параметров или создаю макрос (вздох).</p>

0 голосов
/ 31 марта 2011

Какой лучший способ обойти это?

Вы можете рассмотреть возможность использования значения часового, чтобы вам не приходилось считать элементы - это менее (но все же) подвержено ошибкам. Перегрузка может быть даже лучше с точки зрения безопасности использования, но, очевидно, она становится непрактичной, если число аргументов часто велико и непредсказуемо.

Еще лучше передать vector или массив. С массивами вы можете заставить вызываемую функцию знать размер ala:

template <size_t N>
void f(type (&x)[N])
{
    ...
}

Ваш пример предполагает, что type является константой и известен во время компиляции, поэтому vector или массив должны быть адекватными. Если это упрощение, рассмотрим vector или массив boost variant или any, или, возможно, tuple, или, возможно, функцию, которая возвращает ссылку на объект, чтобы вы могли связывать вызовы, предоставляя последовательные значения ( многие операторы делают это).

...