这是C++0x 右值引用和临时的后续问题
在上一个问题中,我询问了这段代码应该如何工作:
void f(const std::string &); //less efficient
void f(std::string &&); //more efficient
void g(const char * arg)
{
f(arg);
}
由于隐式临时性,似乎应该调用移动重载,这发生在 GCC 而不是 MSVC(或 MSVC 的 Intellisense 中使用的 EDG 前端)。
这段代码呢?
void f(std::string &&); //NB: No const string & overload supplied
void g1(const char * arg)
{
f(arg);
}
void g2(const std::string & arg)
{
f(arg);
}
看来,根据我之前的问题的答案,该功能g1
是合法的(并且被 GCC 4.3-4.5 接受,但不被 MSVC 接受)。但是,GCC 和 MSVC 都拒绝g2
,因为第 13.3.3.1.4/3 条禁止左值绑定到右值引用参数。我理解这背后的基本原理 - 在 N2831“解决右值引用的安全问题”中对此进行了解释。我还认为 GCC 可能正在按照该论文的作者的意图实施该条款,因为 GCC 的原始补丁是由其中一位作者(Doug Gregor)编写的。
但是,我不认为这是非常直观的。对我来说,(a) a在概念上比 aconst string &
更接近a ,并且(b)编译器可以在 中创建一个临时字符串,就好像它是这样写的:string &&
const char *
g2
void g2(const std::string & arg)
{
f(std::string(arg));
}
实际上,有时复制构造函数被认为是隐式转换运算符。在语法上,这是由复制构造函数的形式提出的,标准甚至在第 13.3.3.1.2/4 节中特别提到了这一点,其中派生基转换的复制构造函数被赋予了比其他用户定义的更高的转换等级转换:
将类类型的表达式转换为相同的类类型被赋予精确匹配等级,而将类类型的表达式转换为该类型的基类被赋予转换等级,尽管事实上复制/移动为这些情况调用构造函数(即,用户定义的转换函数)。
(我假设这是在将派生类传递给类似 的函数时使用的,该函数void h(Base)
按值获取基类。)
动机
我提出这个问题的动机类似于如何在添加新的 c++0x 右值引用运算符重载时减少冗余代码(“如何在添加新的 c++0x 右值引用运算符重载时减少冗余代码”)中提出的问题。
如果你有一个函数接受许多可能移动的参数,并且如果可以的话会移动它们(例如工厂函数/构造函数:Object create_object(string, vector<string>, string)
或类似的),并且想要移动或复制每个参数,你很快就开始编写很多代码。
如果参数类型是可移动的,那么可以只编写一个按值接受参数的版本,如上所述。但是,如果参数是(传统)不可移动但可交换的类(如 C++03),并且您无法更改它们,那么编写右值引用重载会更有效。
因此,如果左值确实通过隐式副本绑定到右值,那么您可以只编写一个重载create_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&)
,它或多或少会像提供右值/左值引用重载的所有组合一样工作——作为左值的实际参数将被复制然后绑定对于参数,作为右值的实际参数将直接绑定。
澄清/编辑:我意识到这实际上与按值接受可移动类型的参数相同,例如 C++0x std::string 和 std::vector (除了概念上调用移动构造函数的次数)。但是,对于可复制但不可移动的类型,它并不相同,包括所有具有显式定义的复制构造函数的 C++03 类。考虑这个例子:
class legacy_string { legacy_string(const legacy_string &); }; //defined in a header somewhere; not modifiable.
void f(legacy_string s1, legacy_string s2); //A *new* (C++0x) function that wants to move from its arguments where possible, and avoid copying
void g() //A C++0x function as well
{
legacy_string x(/*initialization*/);
legacy_string y(/*initialization*/);
f(std::move(x), std::move(y));
}
如果g
调用f
,则x
和y
将被复制-我看不到编译器如何移动它们。如果f
改为声明为接受legacy_string &&
参数,它可以避免调用者std::move
在参数上显式调用的那些副本。我不明白这些是如何等效的。
问题
我的问题是:
- 这是对标准的有效解释吗?无论如何,这似乎不是传统的或有意的。
- 它有直觉意义吗?
- 这个想法有我没有看到的问题吗?似乎你可以在不完全预期的情况下悄悄地创建副本,但无论如何这就是 C++03 中的现状。而且,它会使一些重载当前不可行时可行,但我认为这在实践中不是问题。
- 这是一个足够显着的改进,值得为 GCC 制作一个实验性补丁吗?