【问题标题】:Unknown number of arguments in function [duplicate]函数中未知数量的参数[重复]
【发布时间】:2012-06-14 09:44:58
【问题描述】:

我有班级成员:

LineND::LineND(double a ...)
{
    coefficients.push_back(a);
    va_list arguments;
    va_start(arguments, a);
    double argValue;
    do
    {
        argValue = va_arg(arguments, double);
        coefficients.push_back(argValue);
    }while(argValue != NULL);   // THIS IS A PROBLEM POINT!
    va_end(arguments);
}

我不知道将使用多少个参数。我需要将每个参数放入名为coefficients 的向量中。我该怎么做?我了解,在这种情况下,声明 while(argValue != NULL) 是不正确的。例如,我不能使用这个签名:

LineND::LineND(int numArgs, double a ...)

像这样改变条件:

while(argValue != numArgs);

关键是我无法更改方法的签名。需要用另一种方式解决这个问题。

【问题讨论】:

  • 好的,所以你不能改变函数的签名。您至少可以添加到类和函数体中吗?

标签: c++ argument-passing variadic-functions


【解决方案1】:

变量参数列表有几个缺点:

  • 调用者可以传入他们想要的所有内容。
  • 如果传递了非 POD 对象,则会调用未定义的行为
  • 不能依赖参数的数量(调用者可能会出错)
  • 您为您的客户承担了很多责任,您希望为他们更轻松地使用您的库代码(实际示例:format-string-bugs/-errors)

与可变参数模板相比:

  • 编译时间列表大小已知
  • 类型在编译时已知
  • 您应该对事情负责,而不是您的客户,这是应该的。

例子:

void pass_me_floats_impl (std::initializer_list<float> floats) {
    ...
}

您可以将其放入类声明的私有部分或某些详细的命名空间中。注意:pass_me_floats_impl() 不必在标头中实现。

那么这就是为您的客户准备的好东西:

template <typename ...ArgsT>
void pass_me_floats (ArgsT ...floats) {
    pass_me_floats_impl ({floats...});
}

他现在可以做到:

pass_me_floats ();
pass_me_floats (3.5f);
pass_me_floats (1f, 2f, 4f);

但他做不到:

pass_me_floats (4UL, std::string());

因为这会在你的 pass_me_floats-function 中产生一个编译错误。

如果你需要至少,比如说,2 个参数,那么就这样吧:

template <typename ...ArgsT>
void pass_me_floats (float one, float two, ArgsT... rest) {}

当然,如果你想要一个完整的内联函数,你也可以

template <typename ...ArgsT>
void pass_me_floats (ArgsT... rest) {
    std::array<float, sizeof...(ArgsT)> const floaties {rest...};

    for (const auto f : floaties) {}
}

【讨论】:

  • 非常感谢您的详细解释。
【解决方案2】:

可变参数在 C++ 中非常不受欢迎,这是有充分理由的。其中一个原因是无法知道列表的结束位置,因此如果您无法更改函数的签名,您必须指定某种标记值来指示列表的位置结束。如果你不能做这两件事,你就完蛋了。

【讨论】:

    【解决方案3】:

    你应该重写方法来取std::initializer_list&lt;double&gt;

    如果您不能这样做,并且无法添加 count 参数,则解决方法是采用终止参数列表的 sentinel 值,例如0.0NaN(或任何在您的函数上下文中没有意义的值)。

    例如,采用可变数量指针的函数使用NULL 作为标记值;采用结构列表的函数将初始化为 0 的结构作为标记。这在 C API 中相当常见。请参阅http://c-faq.com/varargs/nargs.html,其中给出的示例是execl("/bin/sh", "sh", "-c", "date", (char *)NULL);

    【讨论】:

      【解决方案4】:

      在这种情况下,您无法确定代码中的参数数量。您必须让此类函数的调用者将 doubles 的数量传递给您(我假设第一个 a 参数不包含参数的数量)。例如:

      LineND::LineND(unsigned count, ...)
      {
          va_list arguments;
          va_start(arguments, a);
      

      条件变成了

      while (a--) { ... }
      

      在 C++11 中,您可以使用两个更好的设备来传递未指定数量的双精度数。

      1. passing a vector and initializing it with an initializer-list
      2. variadic templates

      请注意,两者之间存在差异;由于{...} 不是 C++ 中的表达式这一事实,前者在转发时存在一些问题。后者可能会导致代码膨胀,因为它会为每个参数类型组合生成一个函数。

      在 C++03 中,您只能使用帮助对象(例如 Boost.Assignment 库提供的对象)来传递未知数量的参数。

      【讨论】:

      • 我不太确定我是否理解“{...} 不是表达式”。你的意思是像ideone.com/1lZRw 这样的东西吗?编辑:糟糕,他们的 g++ 太旧了:ideone.com/2rbBf
      • @phresnel:我的意思是 ideone.com/mKeCp 所说明的内容(请注意,警告实际上是 c++ 标准的错误,不会通过较新的 gcc):{ ... } 只是一种特殊语法用于初始化,而不是表达式本身(在 C++ 语法中)。您不能方便地将其保存在以后的变量中,也不能从中推断出类型等。您的示例在初始化局部变量时仅以最普通的形式使用初始化器列表,从而避免了所有问题。有关其他不一致之处,请参阅ideone.com/LzJ5x
      【解决方案5】:

      使参数列表的第一个双精度为参数的实际计数。

      【讨论】:

        猜你喜欢
        • 2019-12-15
        • 2016-04-07
        • 1970-01-01
        • 1970-01-01
        • 2015-08-22
        • 2012-08-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多