首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否应按rvalue引用返回rvalue引用参数?

是否应按rvalue引用返回rvalue引用参数?
EN

Stack Overflow用户
提问于 2015-05-07 07:02:48
回答 4查看 4.3K关注 0票数 29

我有一个修改std::string& lvalue引用的函数,返回对输入参数的引用:

代码语言:javascript
复制
std::string& transform(std::string& input)
{
    // transform the input string
    ...

    return input;
}

我有一个助手函数,它允许对rvalue引用执行相同的内联转换:

代码语言:javascript
复制
std::string&& transform(std::string&& input)
{
    return std::move(transform(input)); // calls the lvalue reference version
}

注意,它返回一个rvalue引用

我阅读了一些与返回rvalue引用有关的问题(例如这里这里 ),并得出了这样的结论:这是一个糟糕的实践。

据我所读,似乎大家的共识是,由于返回值rvalue,加上考虑到RVO,按值返回将是同样有效的:

代码语言:javascript
复制
std::string transform(std::string&& input)
{
    return transform(input); // calls the lvalue reference version
}

然而,我也读到返回函数参数会阻止RVO优化(例如这里这里)。

这使我相信,从transform(...)的lvalue引用版本的transform(...)返回值到std::string返回值将发生副本。

对吗?

保留我的std::string&& transform(...)版本更好吗?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2015-05-07 08:44:42

没有正确的答案,但以价值回报更安全。

我读过几个有关返回参考文献的问题,并得出结论,这是一个错误的做法。

返回对参数的引用将契约强加于调用方,

  1. 该参数不能是临时的(这正是rvalue引用所表示的),或者
  2. 返回值不会保留在调用者上下文中的下一个分号之后(当临时程序被销毁时)。

如果调用方传递临时消息并试图保存结果,则会得到一个悬空引用。

据我所读,似乎大家一致认为,由于返回值是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)

你得做:

代码语言:javascript
复制
return std::move( transform( input ) );

或等量

代码语言:javascript
复制
transform( input );
return std::move( input );
票数 13
EN

Stack Overflow用户

发布于 2015-05-07 09:46:42

以上版本的transform的一些(非代表性的)运行时

跑到科里鲁

代码语言:javascript
复制
#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;
}

输出

代码语言:javascript
复制
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

Runtime transformA: 444 ms
Runtime transformB: 796 ms
Runtime transformC: 434 ms
票数 1
EN

Stack Overflow用户

发布于 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(.)好吗?版本?

这有点有趣,也很奇怪。波陶瓦特回答说,

代码语言:javascript
复制
std::string transform(std::string&& input)
{
    return transform(input); // calls the lvalue reference version
} 

您应该调用std::手动移动。

但是,您可以单击developerworks:RVO V.S. std::移动查看更多细节,这清楚地解释了您的问题。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30094067

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档