传递引用、常量引用、右值引用或常量右值引用
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 s3n";
cout << "s3 is now " <<s3;
b = s4;
b = "b changed after assigning s4n";
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
我的问题是:
如果我们传递一个常量表达式,它总是触发非常量右值引用?在什么条件下,它将触发恒定值参考(为什么s6没有触发它?)
为什么非常量引用和常量右值引用是非法的?
我原以为a不能改变s3,但为什么b在内部范围内可以改变s3?如果将一个新对象s3分配给b就是分配一个新的引用,那么为什么我将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 s3n"; //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 s4n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4; //s4 is still blank, it hasn't changed.
}
然后是问题:
如果我们传递一个常量表达式,它总是触发非常量右值引用?在什么条件下,它将触发恒定值参考(为什么s6没有触发它?)
现有对象将作为string&
或const string&
传递,具体取决于它们是否为常量。它们也可以复制为string
。临时文件将作为string&&
传递,但也可以作为string
复制。有触发const string&&
的方法,但从来没有这样做的理由,所以这无关紧要。它们显示在这里。
为什么非常量引用和常量右值引用是非法的?
该标准特别指出,只有const string&
和string&&
才能延长临时工的寿命,尽管我不确定他们为什么没有提到string&
和const string&&
。
我原以为a不能改变s3,但为什么b在内部范围内可以改变s3?如果将一个新对象s3分配给b就是分配一个新的引用,那么为什么我将s4分配给它,s3被更改,之后s4为空呢?
您将b
初始化为对s3
的引用。不是副本,而是参考资料。这意味着b
现在永远引用s3
,无论如何。当您键入b = "b changed after assigning s3n";
时,它与s3 = "b changed after assigning s3n";
完全相同。当您键入b = s4;
时,它与s3 = s4
完全相同。这就是引用。它们不能"重新密封"。
右值可以绑定到右值引用和常量左值引用,例如
void foo(const string&);
void bar(string&&);
foo(string{});
bar(string{});
但是右值不能绑定到非常量的左值引用。重载解析更喜欢将临时值绑定到右值引用,而不是将它们绑定到常量左值引用:
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);
这是因为右值的概念是您可以回收其内容,例如,声明其动态分配的内存的所有权。如果在操作之后仍然可以访问对象,这可能是危险的,因此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 s3n";
cout << "s3 is now " <<s3;
b = s4; // copy the contents of `s4` to `b` (i.e. to `s3`)
b = "b changed after assigning s4n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4;
}
如果我们传递一个常量表达式,它总是触发非常量右值引用?在什么条件下,它将触发恒定值参考(为什么s6没有触发它?)
常量表达式只能包含声明为constexpr
或const
的对象或临时对象(的左值到右值转换),这些对象是右值。因此,AFAIK,一个常量表达式不能产生一个非常常量左值。
为什么非常量引用和常量右值引用是非法的?
事实上,两者都是允许的。虽然const
左值引用对我来说没有任何意义,但你也可以使用const
左值引用。
我原以为a不能改变s3,但为什么b在内部范围内可以改变s3?如果将一个新对象s3分配给b就是分配一个新的引用,那么为什么我将s4分配给它,s3被更改,之后s4为空呢?
我认为您对初始化引用和为您声明为引用的名称赋值之间的区别感到困惑。
只回答这一部分:
在什么条件下,它将触发恒定值参考
当您使用常量类型的右值调用常量右值时,将使用常量右值引用重载:
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&&
的函数是无用的,因为你不能从常数中移动。它们可以用作已删除的函数,以防止编译某些调用。
如果我们传递一个常量表达式,它总是触发非常量右值引用?在什么条件下,它将触发恒定值参考(为什么s6没有触发它?)
对于常量表达式?没有一个只有当某个东西已经是const
时,它才会绑定到const&&
。即便如此,如果它是一个变量,也需要显式强制转换(见下文)。
为什么非常量引用和常量右值引用是非法的?
我想你说的是这些:
//string& s{""};
//paramCheck(s);
//const string&& s{s1};
//onstFP(s);
第一个是非法的,因为""
不是std::string
变量。因此,它必须从""
构造一个std::string
临时。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
之后,不能更改b
引用的对象。b
从引用s3
开始,因此它将始终这样做。因此b = s4
意味着将s4
复制到b
引用的任何对象中,即s3
。
s4
后来是空的,因为它总是空。你给它分配了空字符串。所以它是空的。
您应该停止将Foo&&
视为右值引用。想一想事物的本质。
采用Foo&&
的函数将仅绑定到临时Foo
s或标记为临时的Foo
s。
这种临时标记不会持久。如果您有一个变量Foo&& foo
,并且您使用了它,那么在使用时它不会被标记为临时的。将某个东西标记为临时的只能立即发生——通过返回Foo&&
的函数,或者通过返回匿名Foo
(在其立即使用时被视为临时的)。
将数据标记为临时的标准方法是:(A)它是临时的Foo
的匿名实例,(B)您在Foo
的实例上调用了std::move
,(C)在Foo
的实例上使用了std::forward<Foo>
。
在实践中,&&
既被称为通用引用的引用使用,也被要绑定到临时引用的引用所使用。在类型推导上下文中,通过将T
转换为Foo&
,可以将左值引用存储在T&&
中——左值引用"战胜"了右值引用。在这种情况下,您需要调用std::forward
才能有条件地移动。
简而言之:&&
共有四个常用的有效点。
- 获取函数或方法参数列表中要从中移出的参数时
- 当您在
template
函数的参数中使用完美转发和通用引用技术时 - 当您将一个完美的转发参数传递给一个返回值时
- 当您使用通用引用技术创建引用时,该引用可能是函数范围中的临时引用(例如,
for(auto&& i:x)
)
使用命名的&&
变量时,其作用几乎与&
或const &
变量完全相同。为了以将其视为临时的方式使用它,您需要std::move
,或者在通用引用上下文中,使用std::forward
来有条件地使用std::move
。
- 什么时候在C++中返回常量引用是个好主意
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 为什么我可以通过引用修改常量返回
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- 将常量指针引用绑定到非常量指针
- 通过常量引用传递参数的矩阵模板类
- 按值捕获引用时出现非常量
- 在C++中使用非常量引用作为常量
- 具有常量引用参数的函数模板专用化
- 多个"常量引用"变量可以共享同一个内存吗?
- 为什么 STL 容器适配器堆栈中的 top 返回常量引用?
- 为什么按值传递QStringView比引用常量更快?
- 通过引用常量函数调用另一个类的非常量函数
- 构造常量对象与引用常量对象
- 引用“常量value_type”时出错
- 为什么可以在 for 语句中重新分配引用常量
- 程序反馈:命名循环索引和引用常量数据
- 堆还是栈?在c++中函数调用中引用常量字符串时
- 为什么常量结构数组在按名称引用常量结构时不放在 .rodata 中?
- 为什么编译器允许在函数中发送对迭代器的引用,该函数引用常量迭代器