C ++从具有正式参数'rvalue'引用的函数返回'prvalue',并带有'prvalue'?

时间:2014-11-24 23:01:06

标签: c++ c++11 optimization reference return-value

我想实现一个结构,它在按值返回大型结构时更有效,使用未命名的临时('prvalue')作为默认的形式参数。首先,我将展示它将解决的性能问题的一个例子:

如果我们有结构:

struct BIG_STRUCT
{
    char aArray[260];
    char aArray1[260];
} ;

一个初始化它的临时函数然后按值返回它的函数:

BIG_STRUCT Func()
{
    BIG_STRUCT sTemp;

    /*init sTemp*/

    return sTemp; // or BIG_STRUCT(sTemp) // constructs the return value by using a temporary
}

我相信现在您已经看到了性能问题的位置 - 在堆栈上创建了2个BIG_STRUCT副本 - 1用于临时用于“构造”返回值,1用于返回值本身。同样在return语句中,调用'copy constructor'来使用临时值初始化返回值。因此浪费了内存和执行时间。现在您可能会想为什么不通过引用返回?但是这将导致悬挂引用,因为函数临时值在它们所属的函数范围的末尾被“破坏”(即使引用是'rvalue',它也不会起作用)。例如:

BIG_STRUCT & /* or 'rvalue' one - '&&' */ Func()
{
    BIG_STRUCT sTemp;

    /*init sTemp*/

    return sTemp; // return a local by reference - resulting in dangling reference
}

这个问题的解决方案正如我在开头所说的那样,通过在函数调用时自己创建返回值临时值,使用未命名的变量作为类型为'rvalue'引用的默认形式参数,并再次返回'rvalue'引用这个对象。所以'BIG_STRUCT Func()'的优化版本将如下所示:

BIG_STRUCT &&Func(BIG_STRUCT &&result = BIG_STRUCT())
{
    //BIG_STRUCT sTemp;

    /*init result*/

    return (BIG_STRUCT&&)result; //converts 'lvavlue' to 'xvalue'
}

这将与函数实际返回值但没有性能问题的方式相同。这种结构只有一个问题,即函数返回一个'rvalue'引用,所以如果我们将它的返回值赋给'rvalue'引用,它的生命周期就不会被延长,这将导致一个悬空引用。我的意思是新标准允许我们通过将它们分配给'rvalue'参考来延长'prvalues'的生命周期。但是我们在当前示例中没有返回一个,所以这样的代码将无效:

BIG_STRUCT && refPrvalue = Func();

refPrvalue.aArray[3] = 0; //unknown result accessing dangling reference

任何解决方案我该如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

我认为你可能误解了C ++ 11中按价值返回的方式。

返回具有自动存储持续时间的本地对象将被编译器自动视为rvalue(这是标准所要求的)。

MyObject func() {
    MyObject o;

    return o; // Return value will be move constructed.
}

这是因为编译器知道在退出作用域时o是要被破坏的。因此,在这种情况下明确地将右值传递给return是错误的,例如

return std::move(o); // Wrong, 'o' is implicitly converted to rvalue.

事实上,这将禁止RVO(见下一段),因此是一种悲观情绪。

此外,所有大多数现代编译器都将使用RVO(返回值优化),这将确保返回值在呼叫站点构建。 E.g。

MyObject func() {
    return {}; // Further simplification. Default constructs return value.
}

int main() {
    MyObject o = func(); // You can almost be sure that no copies are made.
                         // 'o' is constructed directly at call site (RVO).
                         // Worst case scenario, one move is performed.
}