繁体   English   中英

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

[英]Is it possible to handle non-primitive types in a variadic function?

我有2个版本的同一个可变参数函数,但是一个版本有效,而另一个版本则无效。 我怀疑原因是因为一个正在使用原始类型,而另一个正在使用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)

因此,第一个功能有效,而第二个无效。 我是否可以正确地假设这是因为可变参数宏不能处理非原始类型? 您可以让它处理非流行类型吗?

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

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

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

问题是您需要知道如何解压缩这些args...它们被称为参数包 打开它们的包装有几种不同的方法。 这种特殊形式称为折叠表达式。

va_arg解码va_list

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

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

const char *arg = va_arg(file_names, const char *);

非平凡类的va_arg是实现定义的

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

如果给定参数没有参数,则以使接收函数可以通过调用va_arg(18.10)来获取参数值的方式传递参数。 …传递具有非平凡的复制构造函数,非平凡的移动构造函数或非平凡的析构函数(没有相应参数)的类类型的可能求值的参数,并且由实现定义的语义有条件地支持。 ...
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...);
}

因此,可以通过递归调用函数来实现迭代,并在下一个递归调用中将参数包减少一个参数。

如果您这样调用test2,它将正常工作:

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

这里发生的是,当您传递“ Hallo”时,您传递的是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: :可变参数中的字符串。

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

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

using std::string_literals;

然后在普通字符串的末尾添加一个“ s”,如下所示:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM