11

我正在学习通过参考传递,这是我所做的测试:

#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. 如果我们传递一个常量表达式,它总是会触发非常量右值引用吗?在什么情况下它会触发常量右值引用(为什么 s6 不触发它?)

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

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

抱歉问了太多问题......当所有问题都得到回答时,我会增加分数:) 参考只是把我的困惑从指针带到了一个全新的水平。


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

4

5 回答 5

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.
}

然后是问题:

如果我们传递一个常量表达式,它总是会触发非常量右值引用吗?在什么情况下它会触发常量右值引用(为什么 s6 不触发它?)

现有对象将作为string&const string&取决于它们是否为 const 传递。它们也可以复制为string. 临时文件将传递为string&&,但也可以复制为string。有触发const string&&方法,但没有理由这样做,所以没关系。它们显示在这里

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

该标准明确规定,只有const string&andstring&&会延长临时工的寿命,尽管我不确定他们为什么没有提到string&and const string&&

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

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

于 2013-07-31T21:02:16.140 回答
6

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

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

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

但是右值不能绑定到非常量左值引用。重载解析更喜欢将临时对象绑定到 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使用左值将它们绑定到右值引用:

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;
}

如果我们传递一个常量表达式,它总是会触发非常量右值引用吗?在什么情况下它会触发常量右值引用(为什么 s6 不触发它?)

常量表达式只能包含(左值到右值的转换)对象声明为constexprorconst或临时对象,它们是右值。因此,AFAIK,常量表达式不能产生非常量左值。


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

实际上,两者都是允许的。尽管constrvalue refs 对我来说没有任何意义,但您也可以使用constlvalue-refs。


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

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

于 2013-07-31T20:58:21.810 回答
4

只是回答这部分:

在什么情况下会触发常量右值引用

当您使用常量类型的右值调用它时,将使用常量右值引用重载:

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) );

一般来说,带 a 的函数const X&&是没用的,因为你不能从一个常数中移动。它们可以用作已删除的函数,以防止某些调用编译。

于 2013-07-31T21:07:59.650 回答
3

如果我们传递一个常量表达式,它总是会触发非常量右值引用吗?在什么情况下它会触发常量右值引用(为什么 s6 不触发它?)

对于常量表达式?没有任何。唯一绑定的时间const&&是它已经是const. 即便如此,如果它是一个变量,则需要显式转换(见下文)。

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

我假设你在谈论这些:

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

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

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

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

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

首先,你可以改变s3就好了。b是对;的引用 s3它们是同一个对象的两个名称。至于其余的,您无法更改创建b后引用的对象bb开始引用s3,因此它将始终这样做。因此b = s4意味着复制s4到任何被 引用的对象b,即s3.

s4之后是空的,因为它总是空的。您将空字符串分配给它。所以它是空的。

于 2013-07-31T21:02:04.987 回答
2

您应该停止将Foo&&其视为右值引用。想一想事物绑定到什么。

采用的函数Foo&&只会绑定到临时Foos,或Foo标记为临时的 s。

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

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

在实践中,&&被称为通用引用和要绑定到临时对象的引用都使用它。在类型推导上下文中,左值引用可以存储在 a 中,T&&方法是:左值引用“胜过”右值引用。这是您需要调用才能有条件地移动的情况。TFoo&std::forward

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

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

使用命名&&变量时,它的行为几乎与 a &orconst &变量完全一样。为了以将其视为临时的方式使用它,您需要std::move或在通用参考上下文中,std::forward有条件地使用 to std::move

于 2013-07-31T21:05:56.117 回答