考虑一个按值获取对象的函数,对它执行一些操作并返回该对象,例如:
std::string MyToUpper (std::string s)
{
std::transform(s.begin(), s.end(), s.begin(), std::toupper);
return s;
}
现在用临时函数调用此函数:
std::string Myupperstring = MyToUpper("text");
从概念上讲,不需要复制。在这种情况下,现代编译器是否能够忽略所有副本?如果没有,是否只有动作?这个案子怎么样:
std::string Mylowerstring("text");
std::string Myupperstring = MyToUpper(std::move(Mylowerstring));
答案 0 :(得分:4)
最多可以省略两个概念副本中的一个。如果你将一个临时函数传递给函数,那么根据C ++ 11 12.8 / 31的第三个项目符号可以省略该副本:
当复制/移动临时类对象...时,可以省略复制/移动操作
回报不能被遗漏;根据第一个项目符号,只能对临时工(按照上面引用的规则)或局部变量进行:
在return语句中...当表达式是a的名称时 非易失性自动对象(除了函数或catch子句参数)...可以省略复制/移动操作
如果没有elision,返回值将被视为 rvalues 并在可能的情况下移动(并且可以在此处移动);如果函数参数是 rvalues ,则它们会被移动,就像它们在你的例子中一样。
答案 1 :(得分:3)
我不这么认为。一些副本可以,也可能是 被淘汰,但NVRO无法申请,因为它依赖于 变量与返回的位置相同 值。除了带有值参数,参数是 由呼叫者构建,谁看不到(一般)那个 参数将被返回,因此无法在其中构造它 正确的地方。
答案 2 :(得分:1)
从概念上讲,不需要复制。在这种情况下,现代编译器是否能够忽略所有副本?
是的,如果函数是内联的,则可以。但是,我想考虑下面的代码示例而不是你的代码示例,因为std::string
是一个充满晦涩优化的野性野兽
因此,让我们考虑使用int
s的示例。来电者有{1, 2, 3}
,并希望从中创建包含std::vector<int>
的{{1}}。 (这大致类似于在调用者处拥有文字 {2, 4, 6}
并希望就地构建包含"text"
的{{1}}。)
代码:
std::string
如果我使用gcc 4.7.2将其编译为:"TEXT"
,我在程序集中只得到一个#include <cstdio>
#include <vector>
using namespace std;
vector<int> mult(vector<int> v) {
for (int& e : v)
e *= 2;
return v;
}
int main() {
vector<int> v( mult({1, 2, 3}) );
for (int i : v)
printf("%d\n", i);
}
析构函数调用。 矢量是就地创建的。生成的程序集尽可能好。
如果我编译完全相同的代码但省略了g++ -O3 -fwhole-program -Wall -Wextra -std=c++11 -S file.cpp
标志,则std::vector<int>
函数不会被内联,我会得到两次对-fwhole-program
的析构函数的调用。生成的程序集也比前一种情况更糟糕。
Clang不知道mult()
标记,因此我将std::vector<int>
关键字添加到-fwhole-program
:
static
然后它也就地创建了矢量。
使用值参数,参数由调用者构造,谁是 看不到(通常)将返回参数,等等 不能在正确的地方建造它。
我上面做的(内联mult()
)使得调用者可以看到该参数将被返回并且实际上,结果是就地构建的。