我有两个版本的相同的可变参数函数,但是一个工作,另一个不工作。我怀疑原因是因为一个使用原始类型,而另一个使用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)
第一个功能因此起作用,但第二个功能不起作用。我是否正确地认为这是因为可变参数宏不处理非原始类型?你能让它处理非原生类型吗?
答案 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
值,则必须将其解码为int
与va_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。