这样安全吗?右值和引用 return 值

Is this safe? Rvalue and reference return values

#include <iostream>
#include <string>

using namespace std;

class Wrapper
{
public:
    std::string&& get() &&
    {
        std::cout << "rvalue" << std::endl;
        return std::move(str);
    }

    const std::string& get() const &
    {
        std::cout << "lvalue" << std::endl;
        return str;
    }
private:
    std::string str;
};

std::string foo()
{
    Wrapper wrap;
    return wrap.get();
}

std::string bar()
{
    Wrapper wrap;
    return std::move(wrap.get());
}

std::string fooRvalue()
{
    return Wrapper().get();
}

std::string barRvalue()
{
    return std::move(Wrapper().get());
}

int main() {
    while(1)
    {
        std::string s = foo();
        std::string s2 = bar();
        std::string s3 = fooRvalue();
        std::string s4 = barRvalue();
    }
    return 0;
}

const std::string& 和 std::string&& 的 return 值在此用例中是否安全(假装 std::string 是其他一些可能不可复制的类型).我认为它不会工作,因为引用应该指向一些超出范围的局部变量,但它似乎工作得很好?我也看过以前使用的 && 语法,我是否正确使用它(我将其限制为 Wrapper 本身是右值时)。

当 return 引用是安全的时候,我只是有点困惑,我认为规则是对象必须比被 returned 的值长,但我见过类似的东西之前的标准库和提升引用。如果我将他们给我的引用存储在一个变量中(这不是引用),如果我然后 return 那个存储的变量,它似乎一切正常。谁能帮我解决一下,给我一些好的规则让我遵守。

这是我正在玩的 ideone,似乎一切正常:http://ideone.com/GyWQF6

函数的 return 值对象将在 销毁在 return 表达式中创建的局部变量和临时变量之前初始化。因此,从 locals/temporaries 移动到函数 return 值是完全合法的。

这四个全局函数起作用是因为它们 return 对象,而不是对对象的引用。并且该对象在其构造函数参数引用被销毁的任何局部变量之前构建。

barRvalue 相当多余(因为 Wrapper().get() 的结果已经是右值引用),但功能正常。

Im just a little generally confused when returning a reference is safe

一个函数return引用一个对象已经被销毁是不安全的。返回对局部对象或局部对象的一部分的引用是不安全的。

返回对 *this*this 的某些部分的引用是安全的,因为 this 对象将比 return 引用的函数更长寿。这与 return 对您通过 pointer/reference 获取的参数的引用没有什么不同。该对象将比 return 引用的函数调用更有效。

是的,这是实现成员的正确且安全的方式getter。但是你可以做一个改进,我会谈到的。关键是 std::string 成员的生命周期(几乎)与包含它的 Wrapper 对象的生命周期相同。临时变量只会在它出现的完整表达式的末尾被销毁,而不是在 "used" 之后立即被销毁。所以在return std::move(Wrapper().get());中,创建Wrapper,调用get(),调用std::move,构造return值,然后才[=16] =] 及其 string 被摧毁。

唯一的危险是如果有人将另一个引用绑定到您的成员。但那将是他们的错误,普通的 const-reference getters 也有同样的危险。这相当于不正确的 const char* ptr = "oops"s.c_str();.

std::string foo()
{
    Wrapper wrap;
    return wrap.get();
}

foo 复制从成员构造 return 值。希望这不是一个惊喜。

std::string bar()
{
    Wrapper wrap;
    return std::move(wrap.get());
}

bar 也从成员复制构造 return 值。这里发生的事情是因为 wrap 是一个左值,所以调用左值 get(),return 是一个 const 左值。 (非正式地,“const std::string&”。)然后 move 将其转换为一个 xvalue,但它仍然是 const。 (非正式地,“const std::string&&”。)移动构造函数 string(string&&) 无法在此处匹配,因为参数是 const。但是拷贝构造函数string(const string&)可以匹配,并被调用。

如果您希望 bar 移动构造 return 值,您可能需要为非常量左值添加第三个重载:

std::string& get() & { return str; }

(如果您不想允许修改成员,请注意您已经这样做了。例如,有人可以这样做 std::move(wrap).get() = "something";。)

此外,如果您有 return std::move(wrap).get();,即使没有第三次重载,也可以移动构造 return 值。

std::string fooRvalue()
{
    return Wrapper().get();
}

fooRvalue 移动构造了 return 值,因为使用了右值 get()。如前所述,Wrapper 的寿命足以使该结构安全。

std::string barRvalue()
{
    return std::move(Wrapper().get());
}

barRvalue中,move调用完全没用。表达式 Wrapper().get() 已经是一个 xvalue,所以 move 只是将类型 std::string 的 xvalue 转换为 ... std::string 类型的 xvalue。不过还是一样安全。