是否可以在可变参数函数中处理非原始类型?

时间:2018-06-13 23:29:13

标签: c++ variables variadic-functions

我有两个版本的相同的可变参数函数,但是一个工作,另一个不工作。我怀疑原因是因为一个使用原始类型,而另一个使用std :: string。

void test1(int f_num, ...)
{
    va_list file_names;
    va_start(file_names, f_num);

    for(int i=0; i<f_num; i++)
    {
        string name = string(va_arg(file_names, char*));
        cout << name << endl;
    }
    va_end(file_names);
}

void test2(int f_num, ...)
{
    va_list file_names;
    va_start(file_names, f_num);

    for(int i=0; i<f_num; i++)
    {
        string name = va_arg(file_names, string);
        cout << name << endl;
    }
    va_end(file_names);
}

int main()
{
    test1(3, "Hallo", "you", "people");
    test2(3, "Hallo", "you", "people");
}

以上结果如下:

Hallo
you
people
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

第一个功能因此起作用,但第二个功能不起作用。我是否正确地认为这是因为可变参数宏不处理非原始类型?你能让它处理非原生类型吗?

3 个答案:

答案 0 :(得分:2)

第一部分的答案是你大部分都是正确的。类类型的使用存在限制,特别是如果它们具有非平凡的复制构造函数(std::string具有)。它可能适用于某些实现而不适用于其他实现。形式上,有条件地支持实现定义的语义

第二部分有点难,但也有点简单。这些varargs是旧的C,而不是类型安全的。 C ++确实有类型安全的变量,通过模板:

template<typename... T>
void test1(T... args)
{
    std::cout << ... << args << std::endl;
}

问题是您需要知道如何解包那些args... - 他们称之为parameter packs。打开包装有几种不同的方法;这种特殊形式称为折叠表达式。

答案 1 :(得分:2)

va_arg解码va_list

您无法使用va_arg在单个go中转换传入的变量参数参数。 va_arg的语法是解码变量参数以匹配传入的类型。例如,如果传入int值,则必须将其解码为intva_arg。如果您尝试将其解码为其他任何内容(例如,double),则会导致未定义的行为。

由于您传入了字符串文字,因此类型应为const char *

const char *arg = va_arg(file_names, const char *);
关于非平凡类的

va_arg是实现定义的

正如MSalter's answer清楚地解释的那样,即使你真的将std::string传递给期望变量参数的函数,它也不一定会起作用,因为语义是实现定义的。

  

当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用va_arg(18.10)来获取参数的值。 ...通过实现定义的语义有条件地支持传递具有非平凡复制构造函数,非平凡移动构造函数或非平凡析构函数的类类型(第9章)的潜在评估参数,其中没有相应的参数。 ...
   C ++。11§[expr.call]¶7

注意:(18.10)定义va_arg,(第9条)是§[class]。

只需使用可变参数模板

您可以使用C ++ 11的可变参数模板参数功能以类型安全的方式实现您想要的效果。假设你实际上想做的不仅仅是打印每个参数,通常的模式是递归遍历参数包,一次解压缩一个参数。

void test3 (int f_num, std::string file_name) {
    std::cout << f_num << ':' << file_name << std::endl;
}

template <typename... T>
void test3 (int f_num, std::string file_name, T...rest) {
    test3(f_num, file_name);
    test3(f_num, rest...);
}

因此,迭代是通过递归调用函数来实现的,在下一次递归调用时将参数包减少一个参数。

答案 2 :(得分:-1)

如果您这样调用test2,它应该可以工作:

test2(3, std::string("Hallo"), std::string("you"), std::string("people"));

这里发生的事情是,当你通过&#34; Hallo&#34;你传递的是char *,而不是std :: string,所以当你尝试通过给它一个带有va_arg的std :: string类型来检索char *时,它会失败,因为该对象不是的std :: string。

另外,在某些编译器中,似乎你无法传递那些不易破坏的类型,当我在cpp.sh中尝试它时它会给出错误

In function 'void test2(int, ...)':
27:23: error: cannot receive objects of non-trivially-copyable type 'std::string {aka class std::basic_string<char>}' through '...'; 
 In function 'int main()':
36:51: error: cannot pass objects of non-trivially-copyable type 'std::string {aka class std::basic_string<char>}' through '...'

虽然看起来这不是标准的一部分,因为我无法找到需要它的任何地方,所以我认为它只是编译器的限制他们正在使用。如果有人知道有关这方面的详细信息,我将使用正确的信息编辑答案。

就像Henri Menke所说,问题似乎是cpp.sh使用的是不符合c ++ 11标准的旧版本,所以如果你使用的编译器至少符合c ++ 11,你可以使用可变参数中的std :: string。

正如miradulo所说,这似乎是一个实现细节,因此它将取决于您的编译器,而不是您正在使用的标准版本。

而且,就像Henri Menke提到的那样,如果你使用

using std::string_literals;
然后把一个&#39;&#39;在正常字符串的末尾:

test2(3, "Hallo"s, "you"s, "people"s);

它将char *转换为std :: string,因此您可以像这样使用它们来调用test2,这是由于string literals