为啥在返回右值引用时给出 C++ 编译器警告?

Posted

技术标签:

【中文标题】为啥在返回右值引用时给出 C++ 编译器警告?【英文标题】:Why give a C++ compiler warning when returning an rvalue reference?为什么在返回右值引用时给出 C++ 编译器警告? 【发布时间】:2015-07-03 10:41:24 【问题描述】:

我一直在研究右值引用(对我来说是一个新概念),并且对我在以下类函数中收到的警告感到困惑......

string&& Sampler::Serial() const 
    stringstream ss;
    .
    . [assemble a string value using data members]
    .
    return ss.str();

这编译成功,但出现以下警告...

..\Metrics\Sampler.cpp:71:16: warning: returning reference to temporary [-Wreturn-local-addr]
  return ss.str();
                ^

我完全清楚我正在返回一个临时的,这一点可以从我使用右值引用作为我的返回类型这一事实来证明。代码在执行时似乎运行良好,那么为什么要保证编译器警告呢?

similar questions 的标准答案似乎是复制返回值而不是使用引用,但是当我可以使用右值引用移动它时,为什么要复制潜在的大量临时数据呢?这不就是它被发明的原因吗?

【问题讨论】:

右值引用仍然是引用;临时没有神奇的生命周期延长,它在控制权返回给Serial()的调用者之前被销毁,所以你返回的引用总是悬空的。 您返回的不是临时的;您正在返回对临时的引用。你也没有移动任何东西。 我不确定我是否看到了 Johanne 对链接问题的回答中未讨论的问题。如果您简单地返回std::stringss.str(),您就不是在“复制可能大量”的任何东西。是什么让您认为如果正确调用移动语义会以某种方式适用? @Syndog 未定义的行为会产生令人讨厌的结果,通常会做你认为应该做的事情,直到它没有做。 @Syndog std::string 支持移动构造;您将从str() 返回的右值移动到您的 res 中,然后从 that 移动到调用者的目标中。 std::string 针对允许移动分配和移动构造进行了优化。简而言之,如果您仅使用std::stringreturn ss.str();,您几乎将不得不跳过障碍来强制这个not 做您想做的事情。 【参考方案1】:

没有移动您的数据。您正在创建一个本地对象,创建对该本地对象的引用,销毁该本地对象,然后仍在使用该引用。

您应该按值返回,正如您已经发现的那样。但不是复制,而是移动数据。这是确保您不会复制大量数据的安全方法。

std::string Sampler::Serial() const 
    std::stringstream ss;
    .
    . [assemble a string value using data members]
    .
    return std::move(ss.str());

注意:std::move 在技术上在这里是多余的,因为ss.str() 已经返回了一个右值,因此已经被移动了。无论如何,我建议保留它。这种方式适用于任何情况,因此您不必考虑使用哪种形式:如果要移动,请写move


正如 T.C. 所指出的,一般而言,尽管您的情况并非如此,但这可以防止 RVO。如果 RVO 是可能的并且编译器无论如何都会隐式使用移动,则无需显式编写 move。例如:

std::string f() 
  std::string x;
  ...
  return x; // not std::move(x)

在这里,读者应该已经清楚x 是一个局部变量。 C++ 代码在不写move 的情况下返回局部变量是正常的,因为编译器将完全忽略x 局部变量并直接在返回槽中构造std::string 对象(无论这对您的平台意味着什么),或者无论如何,编译器都会隐式使用 std::string 的移动构造函数。

【讨论】:

这会抑制 RVO。 @T.C. RVO 在这里已经不可能了,但公平点。如果 RVO 是可能的,请不要写 move。将编辑。 @hvd:这仍然可以防止复制省略,AFAIK。 @Xeo 嗯。是的,我认为你是对的,它会......该死。现在从根本上改变我的答案是不合适的,它会歪曲已经投下的选票(或者我被告知,无论如何),但是如果你想写一个不同的答案来解释为什么 not 在这里写 std::move,你会得到我的支持,然后 OP 希望能接受它。 @hvd 非常同意。我会留意的。【参考方案2】:

这类似于返回一个对局部变量的左值引用,让您快速了解未定义的行为。

无论您返回左值引用还是右值引用,您仍在引用将在函数退出时销毁的内存。

Rvalue 引用返回类型应该保留用于当您引用一个生命周期比函数长的对象但您不再需要它的情况下,因此为了提高效率而将其移出就可以了。例如,您可能会临时存储一些数据,但客户可以选择“窃取”您的数据。在这种情况下,返回对数据的右值引用是合理的。

【讨论】:

给这个 +1 的优秀简单的英语博览会。谢谢,TartanLlama!【参考方案3】:

您返回对在作用域结束时被销毁的临时对象 (ss) 的对象(由 str() 返回)的(右值)引用。

你应该返回对象:

string Sampler::Serial() const

【讨论】:

以上是关于为啥在返回右值引用时给出 C++ 编译器警告?的主要内容,如果未能解决你的问题,请参考以下文章

C11新特性右值引用&&

重新理解C11的右值引用

为啥这个函数在给定右值参数的情况下返回一个左值引用?

在 C++ 中将右值引用转换为临时参数到 const 左值返回的正确方法

c++中的左值和右值,右值引用到底是啥?关于引用这一节看得很迷糊。

书中“右值”和“右值引用”之间的混淆