通过引用,常量引用,rvalue-reference或常量rvalue-reference传递?

时间:2013-07-31 20:41:04

标签: c++ c++11 parameter-passing rvalue-reference

我正在学习通过引用传递,这是我做的测试:

#include <iostream>

using namespace std;

int i = 0;

//If this is uncommented, compiler gives ambiguous definition error.
//void paramCheck (string s) {
//  cout << ++i << ". Param is var.\n";
//}

void paramCheck (const string& s) {
    cout << ++i << ". Param is const ref.\n";
}

void paramCheck (string& s) {
    cout << ++i  << ". Param is non-const ref.\n";
}

void paramCheck (const string&& s) {
    cout << ++i  << ". Param is const rvalue-reference.\n";
}

void paramCheck (string&& s) {
    cout << ++i  << ". Param is non-const rvalue-reference.\n";
}


int main(int argc, char **argv) {
    //Function call test
    paramCheck("");

    paramCheck(string{""});

    string s3{""};
    paramCheck(s3);

    const string s4{""};
    paramCheck(s4);

    //Illegal
    //string& s{""};
    //paramCheck(s);

    const string& s5{s3};
    paramCheck(s5);

    string&& s6{""};
    paramCheck(s6);

    //Illegal
    //const string&& s{s1};
    //onstFP(s);

    //Reference test
    string a = s3;
    a = "a changed s3";
    cout << s3;

    {
    string& b = s3;
    b = "b changed after assigning s3\n";
    cout << "s3 is now " <<s3;

    b = s4;
    b = "b changed after assigning s4\n";
    cout << "s3 is now " <<s3;
    cout << "s4 is now " <<s4;
    }

    cin.get();
    return 0;
}

以下是我得到的结果:

1. Param is non-const rvalue-reference.
2. Param is non-const rvalue-reference.
3. Param is non-const ref.
4. Param is const ref.
5. Param is const ref.
6. Param is non-const ref.
s3 is now b changed after assigning s3
s3 is now b changed after assigning s4
s4 is now

我的问题是:

  1. 如果我们传递一个常量表达式,它总是触发非常量rvalue-reference?在什么条件下它会触发恒定的右值参考(以及为什么s6没有触发它?)

  2. 为什么非常量引用和常量右值引用是非法的?

  3. 我预计一个不能改变s3,但为什么内部范围内的b可以改变s3?如果为b分配一个新对象s3正在分配一个新的引用,那么为什么当我为它分配s4并且s3被更改并且之后s4为空时呢?

  4. 很抱歉提出太多问题......当所有问题都得到解答时,我会增加积分:)这个参考只会让我的指针混乱到一个全新的水平。


    我不知道如何增加这一点...所以会等到2天才有资格获得赏金然后选择答案。

5 个答案:

答案 0 :(得分:11)

首先是代码

paramCheck(""); //constructs a temporary. temporaries bind to `string&&`
paramCheck(string{""}); //constructs a temporary. temporaries bind to `string&&`
string s3{""};
paramCheck(s3); //passes a reference to an existing string: `string&`
const string s4{""};
paramCheck(s4); //passes a reference to an existing string+const: `const string&`
//Illegal
//string& s{""}; //cannot assign a temporary to a non-const l-reference
                 //what would s refer to when the temporary "dies"?
                 //`const string&` would have worked though
//paramCheck(s); //passes a reference to an existing string+const: `const string&`
const string& s5{s3}; //s5 is s3, but with `const`. 
paramCheck(s5); //passes a reference to an existing string+const: `const string&`
string&& s6{""}; //r-references extend the life of temporaries.
paramCheck(s6); //passes a reference to an existing strong: `string&`
//const string&& s{s1}; //temporaries can be extended by `T&&` or `const T&` only.

//Reference test
string a = s3; //a is a _copy_ of s3
a = "a changed s3"; //so changing the copy doesn't effect the origional.
cout << s3; //s3 is still blank, it hasn't changed.

{
string& b = s3; //b isn't really a "reference" to `s3`".  `b` _IS_ `s3`.
b = "b changed after assigning s3\n"; //since `b` IS `s3`, this changes `s3`.
cout << "s3 is now " <<s3;

b = s4; //`b` _IS_ `s3`, so you just changed `s3` again.
b = "b changed after assigning s4\n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4; //s4 is still blank, it hasn't changed.
}

然后问题:

  

如果我们传递一个常量表达式,它总是触发非常量rvalue-reference?在什么条件下它会触发恒定的右值参考(为什么s6没有触发它?)

现有对象将以string&const string&传递,具体取决于它们是否为常量。它们也可以作为string复制。 Temporaries将以string&&传递,但也可以string复制。 触发const string&&的方式,但没有理由这样做,所以没关系。 They're shown here

  

为什么非常量引用和常量右值引用是非法的?

标准明确规定只有const string&string&&会延长临时生活,但我不确定他们为什么不提及string&和{{1} }。

  

我预计一个不能改变s3,但为什么b在内部范围内可以改变s3?如果为b分配一个新对象s3正在分配一个新的引用,那么为什么当我为它分配s4并且s3被更改并且之后s4为空时呢?

您已将const string&&初始化为对b的引用。不是副本,而是参考。这意味着s3现在永远引用b ,无论是什么。当您输入s3时,这与b = "b changed after assigning s3\n";完全相同。当您输入s3 = "b changed after assigning s3\n";时,这与b = s4;完全相同。这就是参考资料。他们不能“重新安置”。

答案 1 :(得分:6)

rvalues可以绑定到rvalue引用和const lvalue引用,例如

void foo(const string&);
void bar(string&&);

foo(string{});
bar(string{});

但是rvalue不能绑定到非const左值引用。重载分辨率更喜欢绑定临时值到rvalue-refs,而不是将它们绑定到const lvalue refs:

void foo(const string&);
void foo(string&&);

foo(string{});           // will call the second overload

lvalues只能绑定到左值引用。但请注意,const限制了这一点:

const string do_not_modify_me;
string& modify_me = do_not_modify_me;  // not allowed, because `do_not_modify_me`
modify_me += "modified";               // shall not be modified: declared as `const`

您可以std::move左右将它们绑定到右值引用:

string s;
string&& r = std::move(s);

这是因为右值的概念是您可以回收其内容,例如:声称拥有动态分配的内存。如果您在操作后仍然可以访问对象,那么这可能很危险,因此左值需要显式的std::move


paramCheck("");         // a string literal is an lvalue (!)
                        // see [expr.prim.general]/1
                        // but it is implicitly converted to a `std::string`,
                        // creating a `string` temporary, a rvalue

paramCheck(string{""}); // a temporary is an rvalue

string s3{""};
paramCheck(s3);         // the variable `s3` is an lvalue of type `string`

const string s4{""};
paramCheck(s4);         // the variable `s4` is an lvalue of type `const string`

//Illegal
//string& s{""};        // can't bind a temporary to a non-const lvalue ref
//paramCheck(s);

const string& s5{s3};
paramCheck(s5);         // the variable `s5` is a lvalue of type `const string`

string&& s6{""};        // binding a temporary to a rvalue-ref (allowed)
paramCheck(s6);         // the variable `s6` is an lvalue (!) - it has a name

//Illegal
//const string&& s{s1}; // `s1` has not been declared
//onstFP(s);

//Reference test
string a = s3;          // copy the contents of `s3` to a new string `a`
a = "a changed s3";     // overwrite contents of `a`
cout << s3;

{
string& b = s3;         // `b` refers to `s3` now (like an alias)
b = "b changed after assigning s3\n";
cout << "s3 is now " <<s3;

b = s4;                 // copy the contents of `s4` to `b` (i.e. to `s3`)
b = "b changed after assigning s4\n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4;
}

  

如果我们传递一个常量表达式,它总是触发非常量rvalue-reference?在什么条件下它会触发恒定的右值参考(为什么s6没有触发它?)

常量表达式只能包含({1}}或constexpr声明的对象的(左值到右值转换),或者是临时值的临时值。因此,AFAIK,常量表达式不能产生非常数左值。


  

为什么非常量引用和常量右值引用是非法的?

实际上,两者都是允许的。虽然const rvalue refs对我没有任何意义,但您也可以使用const左值 - 参考值。


  

我预计一个不能改变s3,但为什么b在内部范围内可以改变s3?如果为b分配一个新对象s3正在分配一个新的引用,那么为什么当我为它分配s4并且s3被更改并且之后s4为空时呢?

我认为你对引用的初始化和分配给你声明为引用的名称之间的区别感到困惑。

答案 2 :(得分:4)

回答这一部分:

  

在什么条件下它会触发恒定的右值参考

当使用常量类型的右值调用它时,将使用常量rvalue-reference重载:

void paramCheck (const string&& s) {
    cout << ++i  << ". Param is const rvalue-reference.\n";
}

const std::string functionThatReturnsConstantRvalue() { return ""; }

// ...

paramCheck( functionThatReturnsConstantRvalue() );

const std::string s;
paramCheck( std::move(s) );

一般来说,使用const X&&的函数是无用的,因为你无法从常量移动。它们可用作已删除的函数,以防止某些调用进行编译。

答案 3 :(得分:3)

  

如果我们传递一个常量表达式,它总是触发非常量rvalue-reference?在什么条件下它会触发恒定的右值参考(为什么s6没有触发它?)

持续表达?没有。只有当const&&已经const时才会绑定到//string& s{""}; //paramCheck(s); //const string&& s{s1}; //onstFP(s); 。即使这样,如果它是一个变量,也需要一个显式的强制转换(见下文)。

  

为什么非常量引用和常量右值引用是非法的?

我假设你在谈论这些:

""

第一个是非法的,因为std::string不是std::string变量。因此,它必须从""构建s临时。 s1是对现有字符串变量的非const引用。您不能将非const引用作为临时引用,因为临时不是变量。

第二个是非法的,因为(忽略了std::move不存在的事实)C ++不允许在没有显式转换的情况下获得对变量的r值引用。这是const string &&s{std::move(s3)}的用途。 s3工作正常。

  

我预计一个不能改变s3,但为什么b在内部范围内可以改变s3?如果为b分配一个新对象s3正在分配一个新的引用,那么为什么当我为它分配s4并且s3被更改并且之后s4为空时呢?

首先,您可以更改bs3引用b;它们是同一个对象的两个名称。至于其余部分,在b创建后,您无法更改b引用的对象。 s3开始引用b = s4,因此总是这样做。因此,s4表示将b复制到s3引用的任何对象,即s4

之后

{{1}}为空,因为始终为空。您为其分配了空字符串。所以它是空的。

答案 4 :(得分:2)

您应该停止将Foo&&视为右值参考。想想与事情有什么关系。

使用Foo&&的功能只能绑定到标记为临时的FooFoo个。

此临时标记不会持久。如果您有变量Foo&& foo,并且使用它,则在使用时不会将其标记为临时变量。将事物标记为临时事件只能立即发生 - 通过返回Foo&&的函数或返回匿名Foo,该函数在其立即使用时被视为临时。

将数据标记为临时的标准方法是(A)它是Foo的临时匿名实例,(B)您在std::move的实例上调用Foo ,(C)您在std::forward<Foo>的实例上调用了Foo

实际上,&&既可以被称为通用引用,也可以通过引用来绑定到临时引用。在类型推导上下文中,通过使T&&成为T,左值引用可以存储在Foo&中 - 左值引用“胜过”右值引用。在这种情况下,您需要调用std::forward才能有条件地移动。

简而言之:使用&&有四个常见的有效位置。

  • 当你想要从函数或方法参数列表中移动参数时。
  • 当您在template函数的参数中使用完美转发和通用引用技术时。
  • 当您将完美转发参数传递给返回值时。
  • 当您使用通用引用技术在函数范围内创建可能临时的引用时(例如,for(auto&& i:x))。

使用命名的&&变量时,它的行为几乎与&const &变量完全相同。为了以一种被视为临时的方式使用它,您需要std::move或在通用参考上下文中使用std::forward来有条件地std::move