本文翻译自:https://docs.microsoft.com/en-us/cpp/cpp/references-cpp?view=vs-2019

  引用,类似于指针,用于存储一个位于内存某处的对象的地址。与指针不同的是,引用在被初始化后不能再指向另一个对象,或设置为null。引用分为两种:左值引用,右值引用,其中左值引用指向一个命名的变量,右值引用指向一个临时对象(temporary object)。操作符&表示左值引用,而&&根据其上下文的不同可表示右值引用或a universal reference。

  注:universal reference:https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

左值引用

作用:持有一个对象的地址,但是其行为类似于一个对象  

格式:type-id & cast-expression

  我们可以将左值引用看为一个对象的别名。左值引用的声明包含一个可选的说明符列表,后面跟一个引用声明符。左值引用必须被初始化,且不能再指向另外一个对象或设置为null。

  任何可以将其地址转换为一个指定类型指针的对象也可将其地址转换为一个类似的引用对象。例如,任何可以转换为char*的对象的地址也可以转换为char &。

  注意不要将引用声明符(&)与地址操作符(取对象的地址,也是&)混淆。当&前面是一个类型时,例如int或char,则该&是一个引用声明符。如果&前面没有任何类型,则该&用于取一个对象的地址。

  • 例子:

  The following example demonstrates the reference declarator by declaring a Person object and a reference to that object. Because rFriend is a reference to myFriend, updating either variable changes the same object.

// reference_declarator.cpp
// compile with: /EHsc
// Demonstrates the reference declarator.
#include <iostream>
using namespace std; struct Person
{
char* Name;
short Age;
}; int main()
{
// Declare a Person object.
Person myFriend; // Declare a reference to the Person object.
Person& rFriend = myFriend; // Set the fields of the Person object.
// Updating either variable changes the same object.
myFriend.Name = "Bill";
rFriend.Age = ; // Print the fields of the Person object to the console.
cout << rFriend.Name << " is " << myFriend.Age << endl;
}

  输出:

Bill is 

右值引用

  右值引用指向临时对象。在C++11之前,可以通过一个左值引用指向一个临时对象,但是这个左值引用必须是const:

string getName()
{
return “Alex”;
}
const string& name = getName();

这也表明,临时对象并不是立即被销毁(destructed),这由C++保证,但它仍然是一个临时对象,你不能修改它的值。

在C++11中,引入了右值引入,可以通过一个可变引用指向rvalue,但是不能绑定到lvalue,因此右值引用可以检测一个值是否是临时对象。

类似于左值引用使用&,右值引用使用&&,可以是const/non-const

  持有一个指向右值表达式的引用。

  •   格式:type-id && cast-expression

  右值引用可以用于区分一个表达式是左值还是右值。左值引用与右值引用在句法上语法上类似,但是遵从不同的规则。下面章节用于描述右值引用是如何支持移动语义(move semantics)和完美转发(perfect forwarding)的实现的。

Move Semantics

  移动语义(move semantics)的实现依赖于右值引用,move可以明显地提升应用的性能。移动语义让你可以通过代码将资源(例如动态分配的内存)从一个对象转移到另一个对象。移动语义可以工作的原理是:它可以将资源从临时对象(temporary objects)中转移到其他地方,这些临时变量在程序的其他地方不会被获取到(be referenced)。

  为了实现移动语义,你需要给自定义的类提供:移动构造函数(move constructor),另外可选地提供移动赋值构造符(move assignment operator, operator=)。(如果实现了这些函数),持有右值资源的对象的拷贝和赋值操作符会自动使用move semantics。与默认的拷贝构造函数不同的是,编译器并不会提供一个默认的移动构造函数。移动构造函数:https://docs.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=vs-2019

  你也可以重载普通函数和操作符来使用move semantics。Visual Studio 2010将move semantics引入到C++标准库中。例如:string类实现了使用move semantics的相关操作。例如:

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std; int main()
{
string s = string("h") + "e" + "ll" + "o";
cout << s << endl;
}

  在Visual Studio 2010之前,string的每一个+操作符都会分配并返回一个新的临时的string对象(an rvalue)。+操作符并不能将一个string扩展到另一个string上,因为它不知道the source string是左值还是右值。如果+左右的两个字符串都是左值,它们可能在程序的其他地方被引用,因此不能修改。通过使用右值引用,可以修改+操作符用于右值,因为右值不会在程序的其他地方被引用。这就可以明显地降低string类必须的动态内存分配。

  当编译器不能使用Reture Value Optimization(RVO),或Named Return Value Optimization时,移动语义也能提升程序性能。在这种情况下,如果返回类型定义了move constructor的话编译器会调用它。

  为了更好地理解移动语义,考虑向vector对象中插入元素这样的一个例子。如果vector对象的容量超出了,vector对象为其存储的元素需要重定位内存,然后将每个元素拷贝到另一个内存地址,以为新插入的元素腾出空间。当插入操作拷贝一个元素时,他会创建一个新的元素,调用拷贝构造函数以将之前的数据拷贝到新元素中,然后再将之前的元素析构掉(destroy)。而移动语义让你可以直接地将元素进行移动,而不需要进行昂贵的内存重定位,和复制操作。为了使用move semantics,你可以写一个move构造函数,用于将数据从一个对象移动到另一个。

Perfect Fowarding

  翻译为:完美转发??? C++11中的一项新技术,

https://blog.csdn.net/u012198575/article/details/83142419

https://docs.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=vs-2019

Perfect forwarding用于降低重载函数的需求,并且当你编写一个参数为引用的泛型函数时,且该泛型函数将参数传递(或:forward)给其他函数时,有助于解决forwarding problem。例如:如果泛型函数的参数类型为const T&,那么对该函数的调用不能修改参数的值。如果泛型函数的参数类型为T&,那么不同使用rvalue(例如:临时对象或整数字面常数:temporary object or integer literal)对该函数进行调用。

对于该类问题的通常解决方法是对该泛型函数进行重载,对于每个参数都重载T& 和const T&的版本。但是,如果函数参数较多的话重载版本会指数型增加。

右值引用rvalue reference可以让你通过一个版本的函数来接收这两种函数,且可通过forward将这些参数传递给其他函数。

例如:对于4中类型:W X Y Z,它们的构造函数分别使用不同的const non-const左值引用组合:

struct W
{
W(int&, int&) {}
}; struct X
{
X(const int&, int&) {}
}; struct Y
{
Y(int&, const int&) {}
}; struct Z
{
Z(const int&, const int&) {}
};

  假如你要编写一个泛型函数来生成这些对象,可以写成下面形式:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
return new T(a1, a2);
}

  通过如下形式可以调用该泛型函数:

int a = , b = ;
W* pw = factory<W>(a, b);

  但是通过如下形式调用会产生错误,因为参数是rvalue,而泛型函数接受的参数是可修改的左值引用(lvalue references that are modifiable)

Z* pz = factory<Z>(, );

  通常解决这类问题的方法是重载:每个参数都有一个A& ,const A&的变化。右值引用则可以让你只写一个版本的函数:

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

这个版本的泛型函数使用右值引用rvalue reference作为factory函数的参数。std::forward函数的作用是forward the parameters of the factory to the constructor of the template class,是将factory函数的参数转发给模板类的构造函数。

该版本可以支持如下的调用方式:

int main()
{
int a = , b = ;
W* pw = factory<W>(a, b);
X* px = factory<X>(, b);
Y* py = factory<Y>(a, );
Z* pz = factory<Z>(, ); delete pw;
delete px;
delete py;
delete pz;
}

Additional Properties of Rvalue Reference右值引用的其他特性

  可以重载一个函数,让它分别接受左值引用和右值引用。

  通过重载一个函数,让它分别接受const左值引用和右值引用,你可以通过代码来判断一个表达式是non-modifiable objects(lvalues)还是modifiable tempoary values(rvalues)。只有当一个对象被标记为const,你才能将它传递给一个参数为右值引用的函数。The following example shows the function f, which is overloaded to take an lvalue reference and an rvalue reference. The main function calls f with both lvalues and an rvalue.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std; // A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
}; void f(const MemoryBlock&)
{
cout << "In f(const MemoryBlock&). This version cannot modify the parameter." << endl;
} void f(MemoryBlock&&)
{
cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
} int main()
{
MemoryBlock block;
f(block);
f(MemoryBlock());
}

该例子输出结果如下:

In f(const MemoryBlock&). This version cannot modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

  在该例子中,第一个调用f函数传递了一个局部变量(右值)作为参数。第二个调用f函数传递了一个临时对象作为参数。由于临时对象不能在程序的其他地方引用,因此调用重载函数中参数为右值的版本,且该版本中过对于传入参数是可以修改的。

  注意:编译器将一个命名的右值引用视为左值进行处理,并将一个未命名的右值引用视为右值进行处理。The compiler treats a named rvalue reference as an lvalue and an unnamed rvalue reference as an rvalue.

  当你编写一个参数为右值引用的函数时,在函数体内该参数被视为是左值。编译器将命名的右值引用当做一个左值进行处理,这是因为一个明明的对象可以在程序的多处被引用;但是允许程序的多处来修改或删除资源时很危险的。例如:如果程序的多个位置都尝试从一个对象中转移资源,仅仅会有一个对象能成功地转移该资源(source)。

  下面例子定义了一个函数g,该函数重载了一个左值引用和右值引用作为参数。函数f的参数为左值引用(一个明明的右值引用),并返回一个右值引用(一个未命名的右值引用)。在f函数中调用g,overload resolution选择了参数为左值引用版本的函数g,因为f的函数体将其参数视为左值。在main函数中调用g,overload resolution选择了参数为优质引用版本的函数g,因为f函数返回的是一个右值引用。

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std; // A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
}; void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
} void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
} MemoryBlock&& f(MemoryBlock&& block)
{
g(block);
return move(block);
} int main()
{
g(f(MemoryBlock()));
}

上面例子的输出为:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

  在该例子中,mian函数传递一个右值给f函数。f函数体将它的参数当做是左值进行处理,函数f调用g时调用的是g的左值引用的版本,因此第一个打印的是左值引用版本中的内容。

  •   你可以将一个左值转换为右值引用

  C++标准库函数std::move允许你将一个对象转换为该对象的右值引用。另外,你可以使用static_cast关键字将一个左值转换为右值引用,看下面的例子:

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std; // A class that contains a memory resource.
class MemoryBlock
{
// TODO: Add resources for the class here.
}; void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
} void g(MemoryBlock&&)
{
cout << "In g(MemoryBlock&&)." << endl;
} int main()
{
MemoryBlock block;
g(block);
g(static_cast<MemoryBlock&&>(block));
}

输出:

In g(const MemoryBlock&).
In g(MemoryBlock&&).
  • 函数模板推断它们的模板参数类型,然后使用引用折叠规则。Function template deduce their template argument types and then use reference collapsing rules.

  编写函数模板然后将其参数再传递给其他函数的行为很常见。理解参数为右值引用的函数模板的模板类型推断(template type deduction)的工作原因很重要。

  如果函数参数是一个右值,编译器会推断该参数为一个右值引用。例如,如果你传递一个类型为x的右值引用给一个参数为T&&的模板函数,模板参数推断(template argument deduction)会将T推断为X。因此,参数类型为X&&。如果函数参数是一个左值或常量左值(const lvalue),则编译器会将类型推断为该类型的左值引用或const左值引用。

  下面例子中声明了一个结构体模板,然后将它实例化为各个类型。print_type_and_value函数的参数为右值引用,然后forward该参数给合适的实例化版本的S::print函数。main函数展示了调用S::print函数的多种方式。

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std; template<typename T> struct S; // The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter. template<typename T> struct S<T&> {
static void print(T& t)
{
cout << "print<T&>: " << t << endl;
}
}; template<typename T> struct S<const T&> {
static void print(const T& t)
{
cout << "print<const T&>: " << t << endl;
}
}; template<typename T> struct S<T&&> {
static void print(T&& t)
{
cout << "print<T&&>: " << t << endl;
}
}; template<typename T> struct S<const T&&> {
static void print(const T&& t)
{
cout << "print<const T&&>: " << t << endl;
}
}; // This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
S<T&&>::print(std::forward<T>(t));
} // This function returns the constant string "fourth".
const string fourth() { return string("fourth"); } int main()
{
// The following call resolves to:
// print_type_and_value<string&>(string& && t)
// Which collapses to:
// print_type_and_value<string&>(string& t)
string s1("first");
print_type_and_value(s1); // The following call resolves to:
// print_type_and_value<const string&>(const string& && t)
// Which collapses to:
// print_type_and_value<const string&>(const string& t)
const string s2("second");
print_type_and_value(s2); // The following call resolves to:
// print_type_and_value<string&&>(string&& t)
print_type_and_value(string("third")); // The following call resolves to:
// print_type_and_value<const string&&>(const string&& t)
print_type_and_value(fourth());
}

上述例子输出为:

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

  为解析每次对函数print_type_and_value函数的调用,编译器首先进行模板变量推断。当编译器将推导的模板参数替换为参数类型时,编译器运用引用折叠规则(reference collapsing rule)。例如,将局部变量s1传递给print_type_and_value函数时,编译器会产生如下的函数签名:

print_type_and_value<string&>(string& && t)

  编译器使用引用折叠规则将签名减少到以下内容:

print_type_and_value<string&>(string& t)

  这个版本的print_type_and_value函数然后forward(转发)它的参数到正确版本的S::print函数。

  下面表格总结了模板变量类型推断的引用折叠规则:

Expanded type Collapsed type
T& & T&
T& && T&
T&& & T&
T&& && T&&

  模板变量类型推断是实现perfect forwarding(完美转发)的重要组成部分。

总结

  右值引用可以用于区分左值,右值。右值引用可以通过消除不必要的内存分配和copy行为来提升你的程序的性能。右值引用还可以让你只写一个版本的函数,就可以接受不同的参数并forward转发该参数到其他函数,就像其他函数被直接调用一样。They also enable you to write one version of a function that accepts arbitrary arguments and forwards them to another function as if the other function had been called directly.

C++左值引用与右值引用的更多相关文章

  1. c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

    为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...

  2. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward

    这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...

  3. 深入学习c++--左值引用和右值引用

    #include <iostream> #include <string> #include <vector> using namespace std; int m ...

  4. c++11 左值引用、右值引用

    c++11 左值引用.右值引用 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #i ...

  5. 左值与右值,左值引用与右值引用(C++11)

    右值引用是解决语义支持提出的 这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运 ...

  6. C++11的左值引用与右值引用总结

    概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...

  7. C++11左值引用和右值引用

    转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&&  对于在C++中,大家 ...

  8. 【C/C++开发】C++11:左值引用VS右值引用

    左值引用VS右值引用 左值引用对于一般的C++程序员再熟悉不过,但对于右值引用(C++0X新特性),就稍微有点不知所云 左值VS右值 在定义变量的时候,经常会用到左值和右值,比如:int a = 1; ...

  9. C++的左值,右值,左值引用,右值引用

    参考大神链接: https://blog.csdn.net/u012198575/article/details/83142419 1.左值与右值 https://msdn.microsoft.com ...

  10. C++11常用特性介绍——左值引用、右值引用

    一.左值.右值 1)左值:可以放在赋值号左侧.可以被赋值的值:左值必须要在内存中有实体. 2)右值:必须放在赋值号右侧.取出值赋值给其它变量:右值可以在内存中也可以在CPU寄存器中. 二.引用 引用是 ...

随机推荐

  1. Markdown 主题修改

    加粗字体的颜色修改 strong, b{ color: #111111; } 斜体的颜色修改 em, i { color: #111111; } 高亮字体的背景颜色修改 #write mark { b ...

  2. Dedecms中{dede:type}标签支持调用父级栏目名称

    需求: 我们用{dede:type}标签调用栏目相关内容时,同时需要调用该栏目的父级栏目的名称. {dede:type}标签的代码做了一下开发,支持这个调用了. 开发方法: 1.打开include/t ...

  3. cookie常用函数

    cookie常用函数 web_add_cookie:添加新的cookie或修改已经存在的cokkies web_remove_cookie:删除指定的cookie web_cleanup_cookie ...

  4. (二)groupId和artifactId

    groupId一般分为多个段,这里我只说两段,第一段为域,第二段为公司名称. 域又分为org.com.cn等等许多,其中org为非营利组织,com为商业组织. 举个apache公司的tomcat项目例 ...

  5. tcpdump使用和抓包分析

    参考资料: http://www.cnblogs.com/ggjucheng/archive/2012/01/14/2322659.html tcpdump可以将网络中传送的数据包的“头”完全截获下来 ...

  6. Python itchat.get_chatrooms() 抓取群聊不全的问题

    1 rooms = itchat.get_chatrooms() 2 f = codecs.open("3.txt","w","utf-8" ...

  7. cb30a_c++_STL_算法_查找算法_(3)search_find_end

    cb30a_c++_STL_算法_查找算法_(3)search_find_endsearch()pos = search(ideq.begin(), ideq.end(), ilist.begin() ...

  8. sql 语句和实例

    修改字段格式的sql语句: alter table tablename alter column colname newDataType 比如:alter table mytable alter co ...

  9. maven测试配置是否成功

    测试maven 是否安装成功 win+r 输入 cmd 调出doc 输入 mvn -v maven安装完成测试报错 后来发现是java-home在path中配置缺少bin,如下图 这个是 java-h ...

  10. BZOJ 3573米特运输

    Description 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题.D星上有N个城市,我们将其顺序编号为1到N,1号城市为 ...