这样安全吗? Rvalue和引用返回值

时间:2017-06-19 23:29:41

标签: c++ c++11 reference c++14 rvalue

#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&amp;和std :: string&amp;&amp;的返回值在这个用例中是安全的(假装std :: string是一些可能无法复制的其他类型)。我认为它不会起作用,因为引用应该指向一些超出范围的局部变量,但它似乎工作得很好?我也见过&amp;&amp;之前使用过的语法,我是否正确使用它(我将它限制为当Wrapper本身是一个右值时)。

我只是有点混淆当返回引用是安全的时候,我认为规则是对象必须比返回的值更长,但我已经看过像标准库这样的东西并且之前提升了returing引用。如果我存储引用它们给我一个变量(这不是一个引用),如果我然后返回该存储的变量,它似乎都工作正常。有人可以解决我的问题,并给我一些好的规则来跟进。

这是我正在玩的想法,一切似乎都有效:http://ideone.com/GyWQF6

2 个答案:

答案 0 :(得分:3)

在销毁return表达式中创建的局部变量和临时值之前,函数的返回值对象将被初始化。因此,从本地/临时值转换为函数返回值是完全合法的。

这四个全局函数可以工作,因为它们返回对象,而不是对象的引用。并且该对象在其构造函数参数引用被销毁的任何本地之前构造。

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

  

在返回引用是安全的时候,我只是有点困惑

返回对已销毁的对象的引用的函数是不安全的。返回对本地对象或本地对象的一部分的引用是不安全的。

返回对*this*this的某些部分的引用是安全的,因为this对象将比返回引用的函数更长。它与返回对指针/引用所引用参数的引用没有什么不同。该对象将比返回引用的函数调用更长。

答案 1 :(得分:3)

是的,这是实现成员getter的正确而安全的方法。但是你可以做一个改进,我会得到。关键是const user$ = name => // returns a new observable Observable.create(o => gun.get(name).on(v => { o.next(v); // passes any new values to the observers console.log(v); }), ); // now you can do rx stuff on the stream of values user$('something')) .map(({ name }) => ({ name: name.toUpperCase() })) .filter(({ name }) => name.length > 0) 成员的生命周期(几乎)与包含它的std::string对象的生命周期相同。而临时只会在它出现的完整表达结束时被销毁,而不是在它被“使用”之后立即销毁。因此,在Wrapper中,return std::move(Wrapper().get());被创建,Wrapper被调用,get()被调用,返回值被构造,只有std::move及其Wrapper string被摧毁。

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

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

foo copy构造成员的返回值。希望这不是一个惊喜。

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

bar也复制构造成员的返回值。这里发生的事情是,由于wrap是左值,因此调用左值get(),返回const左值。 (非正式地,“const std::string&”。)然后move将其转换为x值,但它仍然是const。 (非正式地,“const std::string&&”。)移动构造函数string(string&&)在这里不匹配,因为参数是const。但是复制构造函数string(const string&)可以匹配,并且被称为。

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

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

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

此外,如果你改为return std::move(wrap).get();,即使没有第三次重载,也会移动构造返回值。

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

fooRvalue move构造返回值,因为使用了右值get()。如前所述,Wrapper的寿命足以使这种结构安全。

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

barRvalue中,move来电绝对没用。表达式Wrapper().get()已经是xvalue,因此move只需将类型为std::string的xvalue转换为... {x}类型的xvalue。尽管如此,仍然是安全的。