繁体   English   中英

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

[英]Pass by reference, constant reference, rvalue-reference, or constant 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为空?

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


我不知道如何增加点...所以将等待2天,直到有资格获得赏金,然后选择答案。

首先是代码

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&& ,但是没有理由这样做,所以没关系。 他们在这里展示

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

该标准明确指出,只有const string&string&&将延长临时工的生命,虽然我不确定为什么他们也没有提到string&const string&&

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

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

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

左值只能绑定到左值引用。 但请注意, 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 lvalues将它们绑定到rvalue引用:

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

这是因为rvalue的概念是您可以回收其内容,例如声明它已动态分配的内存的所有权。 如果在操作后仍然可以访问对象,那么这可能很危险,因此lvalues需要显式的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没有触发它?)

常量表达式只能包含(声明为constexprconst对象的lvalue-to-rvalue转换)或临时值(rvalues)。 因此,AFAIK,常量表达式不能产生非常数左值。


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

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


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

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

只是回答这部分:

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

当使用常量类型的右值调用它时,将使用常量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&&const X&&是无用的,因为你无法从常数中移动。 它们可用作已删除的函数,以防止某些调用进行编译。

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

为了一个恒定的表达? 没有。 只有当它已经是const时,才会绑定到const&& 即使这样,如果它是一个变量,也需要一个显式的强制转换(见下文)。

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

我假设你在谈论这些:

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

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

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

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

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

首先,你可以改变s3就好了。 b是对s3引用 ; 它们是同一个对象的两个名称。 至于其他的,你不能改变什么对象是通过引用b之后b被创建。 b开始引用s3 ,因此它总是这样做。 因此, b = s4意味着将s4复制到b引用的任何对象中,即s3

s4是空的,因为它总是空的 您为其分配了空字符串 所以它是空的。

你应该停止将Foo&&视为右值参考。 想想与事物绑定的东西。

使用Foo&&的函数只会绑定到临时Foo ,或者标记为临时的Foo

此临时标记不会持续。 如果你有一个变量Foo&& foo ,并且你使用它,它在使用时不会标记为临时。 将某事标记为临时性只能立即发生 - 通过返回Foo&&的函数,或通过返回匿名Foo ,在其立即使用中,该Foo被认为是临时的。

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

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

简而言之:有四个常用的有效点可以使用&&

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM