我有一个函数可以就地修改std::string&
左值引用,返回对输入参数的引用:
std::string& transform(std::string& input)
{
// transform the input string
...
return input;
}
我有一个辅助函数,它允许对右值引用执行相同的内联转换:
std::string&& transform(std::string&& input)
{
return std::move(transform(input)); // calls the lvalue reference version
}
请注意,会返回右值引用。
我已经阅读了几个关于返回右值引用的问题(例如here和here),并得出结论这是不好的做法。
根据我的阅读,似乎共识是因为返回值是 rvalues,再加上考虑到RVO,只需按值返回就会有效:
std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}
但是,我还读到返回函数参数会阻止RVO优化(例如here和here)
这使我相信,从std::string&
左值参考版本的transform(...)
返回值到std::string
返回值,会发生副本。
这是对的吗?
保留std::string&& transform(...)
版本会更好吗?
答案 0 :(得分:8)
没有正确的答案,但按价值返回更安全。
我已经阅读了几个关于返回右值引用的SO的问题,并得出结论这是不好的做法。
返回对参数的引用会使调用者的合同成为
如果调用者传递临时值并尝试保存结果,则会获得悬空引用。
根据我的阅读,似乎共识是因为返回值是rvalues,再加上考虑RVO,只需按值返回就会有效:
按值返回会添加移动构造操作。其成本通常与物体的大小成比例。虽然通过引用返回只需要机器确保一个地址在寄存器中,但按值返回需要将参数std::string
中的几个指针归零并将它们的值放入要返回的新std::string
中。
它便宜,但非零。
标准库目前采用的方向有点令人惊讶,是快速且不安全并返回参考。 (我知道实际执行此操作的唯一函数是来自std::get
的{{1}}。)实际上,我已经向C ++核心语言委员会提交a proposal以解决此问题, a revision正在进行中,就在今天,我已开始调查实施情况。但它很复杂,而且不确定。
<tuple>
编译器不会在此处生成std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}
。如果move
根本不是参考,那么您会input
,但它没有理由相信return input;
只会因为transform
而返回input
参数,它不会从rvalue引用类型中推断出所有权。 (见C ++14§12.8/ 31-32。)
你需要这样做:
return std::move( transform( input ) );
或等效
transform( input );
return std::move( input );
答案 1 :(得分:1)
以上版本的main() {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("num", new NumSpout(), 10);
builder.setBolt("judge", new Bolt(), 3).shuffleGrouping("num");
builder.setBolt("odd", new Bolt_odd(), 2).shuffleGrouping("judge");
builder.setBolt("even", new Bolt_even(), 2).shuffleGrouping("judge");
}
的一些(非代表性)运行时:
transform
<强>输出强>
#include <iostream>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
using namespace std;
double GetTicks()
{
struct timeval tv;
if(!gettimeofday (&tv, NULL))
return (tv.tv_sec*1000 + tv.tv_usec/1000);
else
return -1;
}
std::string& transform(std::string& input)
{
// transform the input string
// e.g toggle first character
if(!input.empty())
{
if(input[0]=='A')
input[0] = 'B';
else
input[0] = 'A';
}
return input;
}
std::string&& transformA(std::string&& input)
{
return std::move(transform(input));
}
std::string transformB(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}
std::string transformC(std::string&& input)
{
return std::move( transform( input ) ); // calls the lvalue reference version
}
string getSomeString()
{
return string("ABC");
}
int main()
{
const int MAX_LOOPS = 5000000;
{
double start = GetTicks();
for(int i=0; i<MAX_LOOPS; ++i)
string s = transformA(getSomeString());
double end = GetTicks();
cout << "\nRuntime transformA: " << end - start << " ms" << endl;
}
{
double start = GetTicks();
for(int i=0; i<MAX_LOOPS; ++i)
string s = transformB(getSomeString());
double end = GetTicks();
cout << "\nRuntime transformB: " << end - start << " ms" << endl;
}
{
double start = GetTicks();
for(int i=0; i<MAX_LOOPS; ++i)
string s = transformC(getSomeString());
double end = GetTicks();
cout << "\nRuntime transformC: " << end - start << " ms" << endl;
}
return 0;
}
答案 2 :(得分:0)
如果您的问题是纯粹的优化导向,最好不要担心如何传递或返回参数。编译器非常智能,可以将代码扩展为纯引用传递,复制省略,函数内联,甚至移动语义,如果它是最快的方法。
基本上,移动语义可以在一些深奥的案例中受益。假设我有一个矩阵对象,它将double**
作为成员变量保存,并且此指针指向一个两维的double
数组。现在让我说我有这样的表达:
Matrix a = b+c;
复制构造函数(或者在这种情况下为分配运算符)将获得b
和c
之和作为temorary,将其作为const引用传递,重新分配m*n
量doubles
1}}在a
内部指针上,然后,它将在a+b
sum-array上运行,并将逐个复制其值。
简单的计算表明它可能需要O(nm)
个步骤(可以归为O(n^2)
)。移动语义只会将隐藏的double**
隐藏的a
重新连接到O(1)
内部指针。它需要std::string
。
现在让我们暂时考虑O(1)
:
将它作为引用传递需要char*
个步骤(获取内存地址,传递它,取消引用等等,这不是任何形式的线性)。
将它作为r值引用传递需要程序将其作为引用传递,重新连接隐藏的底层C - size
,它保存内部缓冲区,使原始缓冲区为空(或在它们之间交换),复制{ {1}}和capacity
以及更多操作。我们可以看到,虽然我们仍然在O(1)
区域 - 但实际上可以有更多的步骤而不是简单地将其作为常规参考传递。
好吧,事实是我没有对它进行基准测试,这里的讨论纯粹是理论上的。从来没有,我的第一段仍然是真的。我们假设许多东西都是开发人员,但除非我们将所有东西都标记为死亡 - 编译器在99%的时间内比我们更清楚
把这个论点变成acount,我会说它将它作为引用传递而不是移动语义,因为它的后缀兼容,对于那些还没有掌握C ++ 11的开发人员更加理解。
答案 3 :(得分:0)
这让我相信一个副本会发生在std :: string&amp; 将transform(...)的左值引用版本的值返回到 std :: string返回值。
这是对的吗?
返回引用版本不会让std :: string复制,但如果编译器不执行RVO,则返回值版本将具有副本。但是,RVO有其局限性,因此C ++ 11添加了r值引用并移动构造函数/赋值/ std :: move来帮助处理这种情况。是的,RVO比移动语义更有效,移动比复制更便宜但比RVO更贵。
保持我的std :: string&amp;&amp;更好吗? transform(...)version?
这有点奇怪。正如Potatoswatter回答的那样,
std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}
你应该手动调用std :: move。
但是,您可以点击此developerworks链接:RVO V.S. std::move以查看更多详细信息,以便清楚地解释您的问题。