我有一个修改std::string& lvalue引用的函数,返回对输入参数的引用:
std::string& transform(std::string& input)
{
// transform the input string
...
return input;
}我有一个助手函数,它允许对rvalue引用执行相同的内联转换:
std::string&& transform(std::string&& input)
{
return std::move(transform(input)); // calls the lvalue reference version
}注意,它返回一个rvalue引用。
我阅读了一些与返回rvalue引用有关的问题(例如这里和这里 ),并得出了这样的结论:这是一个糟糕的实践。
据我所读,似乎大家的共识是,由于返回值是rvalue,加上考虑到RVO,按值返回将是同样有效的:
std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
}然而,我也读到返回函数参数会阻止RVO优化(例如这里和这里)。
这使我相信,从transform(...)的lvalue引用版本的transform(...)返回值到std::string返回值将发生副本。
对吗?
保留我的std::string&& transform(...)版本更好吗?
发布于 2015-05-07 08:44:42
没有正确的答案,但以价值回报更安全。
我读过几个有关返回参考文献的问题,并得出结论,这是一个错误的做法。
返回对参数的引用将契约强加于调用方,
如果调用方传递临时消息并试图保存结果,则会得到一个悬空引用。
据我所读,似乎大家一致认为,由于返回值是rvalue,加上考虑到RVO,仅按值返回将是同样有效的:
按值返回将增加移动-构造操作。这样做的成本通常与物体的大小成正比。通过引用返回只需要机器确保一个地址在寄存器中,而按值返回则需要对参数std::string中的两个指针进行归零,并将它们的值放入要返回的新std::string中。
很便宜,但不是零。
标准库目前所采取的方向,有点令人惊讶的是,是快速和不安全的,并返回引用。(我所知道的唯一真正做到这一点的函数是来自<tuple>的<tuple>。)碰巧,我已经向提案核心语言委员会介绍了解决这个问题的方法,修订正在进行中,就在今天,我已经开始研究实现。但这很复杂,也不确定。
std::string transform(std::string& input) {返回转换(输入);//调用lvalue引用版本}
编译器不会在这里生成move。如果input根本不是一个引用,并且您做了return input;,那么它就会引用,但是没有理由相信transform仅仅因为它是一个参数就会返回input,而且它也不会从rvalue引用类型中推断出所有权。(见C++14§12.8/31-32)
你得做:
return std::move( transform( input ) );或等量
transform( input );
return std::move( input );发布于 2015-05-07 09:46:42
以上版本的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;
}输出
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Runtime transformA: 444 ms
Runtime transformB: 796 ms
Runtime transformC: 434 ms发布于 2015-11-12 14:44:32
这使我相信一个副本会发生在std::string&转换的lvalue引用版本的返回值(.)到std::string返回值中。 对吗?
返回引用版本不会让std::string复制发生,但是如果编译器不执行RVO,则返回值版本将有副本。但是,RVO有其局限性,因此C++11添加r值引用并移动构造函数/赋值/ std::move来帮助处理这种情况。是的,RVO比移动语义更高效,移动比复制更便宜,但比RVO更昂贵。
保留我的std::string&& transform(.)好吗?版本?
这有点有趣,也很奇怪。波陶瓦特回答说,
std::string transform(std::string&& input)
{
return transform(input); // calls the lvalue reference version
} 您应该调用std::手动移动。
但是,您可以单击developerworks:RVO V.S. std::移动查看更多细节,这清楚地解释了您的问题。
https://stackoverflow.com/questions/30094067
复制相似问题