目录
一、左值与右值
1、类型
左值 与 右值是C++中表达式的属性,在C++11中,每个表达式有两个属性: 类型(type,除去引用特性,用于类型检查)和 值类型(value category,用于语法检查,比如一个表达式结果是否能被赋值)。
2、值类型
值类型包括3个基本类型:lvalue、prvalue与xrvalue,后两者又统称为rvalue,lvalue我们称为左值,可以将左值看成是一个可以获取地址的量,它可以用来标识一个对象或函数,rvalue称为右值,可以认为所有不是左值的量就是右值,prvalue就是纯粹的右值,比如字面量,xrvalue指的是可以被重用的临时对象。
二、右值引用
1、概念
在C++98中,临时量( 术语为右值,因其出现在赋值表达式的右边 )可以被传给函数,但只能被接受为const &类型。这样函数便 无法区分传给const &的是真实的右值还是常规变量,而且,由于类型为const &,函数也无法改变所传对象的值
在C++11引入右值引用,记作typename &&,这种类型 可以被接受为非const值,从而允许改变其值
右值引用 是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石:
2、移动语义
将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)
#include <iostream>
#include <string>
#include <utility>
int main ()
{
std::string &&rr = "World";
{
std::string s = "Hello";
rr = std::move(s ); // rr获取s的资源 //把一个左值移到为右值引用
s = "!!!";
//std::cout << s;
/* 请注意,移动意味着被移出的对象保持在有效但未指定的状态。
这意味着,在这样一个操作之后,移出对象的值只应该被销毁或赋一个新值;
否则访问它将产生一个未指定的值。 */
}
std::cout << rr;
return 0;
}
// move example
#include <utility> // std::move
#include <iostream> // std::cout
#include <vector> // std::vector
#include <string> // std::string
int main () {
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;
myvector.push_back (foo); // copies
myvector.push_back (std::move(bar)); // moves
std::cout << "myvector contains:";
for (std::string& x:myvector) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}
3、完美转发
定义一个函数模板,该函数模板可以接收任意类型参数,然后将参数转发给其它目标函数,且保证 目标函数接受的参数其类型与传递给模板函数的类型相同。
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a); //如果参数a是左值引用,则该函数返回类型不变的a // [lvalue][lvalue]
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0); //否则,函数返回一个右值引用(T&&),该引用指向可用于传递右值的参数//[lvalue][rvalue]
std::cout << '\n';
return 0;
}
#include <utility>
#include <iostream>
#include <type_traits> //这个头文件定义了一系列在编译时获取类型信息的类
/* void overloaded_fn(int& x){ //左值引用
x = 111;
}
void overloaded_fn(int&& x){//右值引用
x = 222;
} */
template<typename T>
void overloaded_fn(T&& x){
x = 888;
}
template<typename T> // 实参为左值,T 为 int& //实参为右值,T为int
void f( T&& x){ //T 为 int&,函数参数引用折叠//int& && int&
//is_same是标识T是否与U相同类型的Trait类
if(std::is_same<T, int& >::value ) std::cout << "int&";
if(std::is_same<T, int >::value ) std::cout << "int";
overloaded_fn(std::forward<T>(x) ); //左值转发左值,右值转发右值// 完美转发
}
/* using T = int&;
void f( T&& x ){ //引用折叠
std::cout << "void f( int&& x)\n";
} */
int main ()
{
int a = 333;
f( a );
//f(std::move(a) );
std::cout << a;
return 0;
}
std::forward保持实参的类型,保持实参的左值/右值属性
#include <utility>
#include <iostream>
#include <type_traits>
void overloaded_fn(const int& x){ //左值引用
std::cout << "void overloaded_fn(const int& x)";
}
void overloaded_fn(int&& x){ //右值引用
std::cout << "void overloaded_fn(int&& x)";
}
template<typename T> //实参的所有性质(T),包括实参是否是const的以及实参是左值还是右值
void f( T&& x){
//is_same是标识T是否与U相同类型的Trait类
if(std::is_same<T, int& >::value ) std::cout << "int&, ";
if(std::is_same<T, const int& >::value ) std::cout << "const int&, ";
if(std::is_same<T, int >::value ) std::cout << "int, ";
overloaded_fn( std::forward<T>(x) ); //右值,不用forward,会匹配 void overloaded_fn(const int& x)
//std::forward保持实参的类型,保持实参的左值/右值属性
}
int main ()
{
const int a = 333;
f( 15 ); //右值
//f(a ); //左值 const int&
return 0;
}
可变参数模板与完美转发
#include <utility>
#include <iostream>
template<typename Func, typename... Args>
void test(Func f, Args&&... args )
{
f(std::forward<Args>(args)... );
}
void func(int a, int &b){
a++;
b++;
}
void func(int &a, int &b){
a++;
b++;
}
int main ()
{
int a=3;
int b=5;
using F1 = void (*)(int,int& );
using F2 = void (*)(int&,int&);
F1 f1 = &func;
F2 f2 = &func;
//test(f1, a, b ); //传左值
//test(f1, 15, b ); //传右值、左值
test(f2, a, b ); //传左值
std:: cout << a << "," << b;
return 0;
}