C++11右值引用理解

C++11之前只有引用这个概念,不存在什么左值引用右值引用。C++11后更新了众多新特性,其中右值引用较为重要,这里对右值引用做一个学习记录。

1 左值与右值

1.1左值

左值比较好理解,能够取到地址的变量、对象等都是左值。

1.2 右值

右值即不能取到地址的对象,此类对象大都为临时变量,比如=右边的值,表达式,函数返回值

int x = 10,y = 10;      //以此为代表的大部分‘=’右边的对象,也称作字面量
&10;                     //报错
string s = "hello world";//右值不包括字符串,对字符串取地址是可以取到值的
&“hello world”;          //不报错

x+y;//x+y也是右值
&(x+y);//报错

int getSum(int x,int y){ 
	int sum = x+y;
	return sum;
  }//注意,这里指的函数返回值不是sum,而是函数返回给调用他的上级函数时的值(临时变量)
&getSum(x,y);//报错,大部分函数返回值都不能取地址,但也有部分能取地址,如返回引用的函数

2、左值引用,右值引用

对左值的引用即作指引用;对右值的引用即右值引用。

int a=0;
int &left_ref=a;//左值引用
int &&right_ref=0;//右值引用
int&& sum = getSum(x,y);//sum是右值引用

3、问题铺垫,便于理解

1、引用占用内存空间吗?

从定义的角度来看,引用是变量的别名,不占用内存空间;但从引用的实现来看,引用是通过指针实现,指针是占用内存空间的,所以引用必定占用内存空间。
2、左右值引用能被哪些值赋值

/*左值引用可以被如下3种值赋值,也就是说左值引用做形参可以传下列值*/
int a = 1;
int &left_ref = a;//左值
int &left_ref2 = left_ref ;//左值引用

int &&right_ref = 10;
const int &left_ref3 = right_ref;//右值引用

/*右值引用可以被如下3种方式赋值:1右值,2右值,3右值。
没错,只能被右值赋值,右值引用也不能赋值右值引用*/
int &&right_ref = 10;//右值
int &&right_ref2 = right_ref;//报错:无法将右值引用绑定到左值。
							 //从以上报错来看,右值引用其实是一种左值

3、函数返回过程

int getSum(int x,int y){
	return x+y;
}
在以上函数中,函数进行返回时,将x+y的值进行拷贝,构造出一个临时变量,
该函数也以这个临时变量返回给上一级调用者,返回完成后(上级函数调用该函数代码行执行完),这个临时变量被销毁。
如果返回值是类的对象,则会调用构造函数进行拷贝,析构函数进行销毁。

4、左值和右值能否互相转换

//左值转右值
int i = 0;
int &&k = static_cast<int&&>(i); 
int &&m = std::move(i);//本质还是上面的强转,但更建议使用这个

//右值不能转换为左值,但右值引用就已经是左值了
int move(int &&val){//这里形参是右值引用,但传入函数后,相对这个函数来说它是左值
	&val;//不报错
}

4、右值引用的作用

右值引用新特性的出现肯定是为开发者提供更多的便利,提高C++效率,主要作用体现在以下两处。

4.1 移动语义

移动语义其实就是为了减少重复的内存拷贝,而使用移动的方式实现资源转移。这种方式被称为移动拷贝,在开发中具体体现为移动拷贝函数或者移动运算符重载函数。

假如有以下函数调用过程(编译需要添加-fno-elide-constructors选项,否则临时变量构建过程看不到,即关闭RVO函数返回值优化):

#include 
#include 

class BigMemoryPool {
public:
    static const int PoolSize = 4096;
    BigMemoryPool() : pool_(new char[PoolSize]) {}
    ~BigMemoryPool()
    {
        if (pool_ != nullptr) {
        delete[] pool_;
        }
    }
    BigMemoryPool(const BigMemoryPool& other) : pool_(new char[PoolSize])
    {
        std::cout << "copy big memory pool." << std::endl;
        memcpy(pool_, other.pool_, PoolSize);
    }
private:
    char *pool_;
};

BigMemoryPool get_pool(const BigMemoryPool& pool)
{
    return pool;
}

BigMemoryPool make_pool()
{
    BigMemoryPool pool;
    return get_pool(pool);
}

int main()
{
    BigMemoryPool my_pool = make_pool();
}

输出结果:
copy big memory pool.
copy big memory pool.
copy big memory pool.

这里可以看到,执行了3次拷贝构造函数,但其实都是中间量,生命周期极短,并没有什么实际意义,如果能够进行移动的话至少可以减少几次拷贝,所以我们添加一个移动构造函数:

#include 
#include 

class BigMemoryPool {
public:
    static const int PoolSize = 4096;
    BigMemoryPool() : pool_(new char[PoolSize]) {}
    ~BigMemoryPool()
    {
        if (pool_ != nullptr) {
        delete[] pool_;
    }
    }
    BigMemoryPool(BigMemoryPool&& other)
    {
        std::cout << "move big memory pool." << std::endl;
        pool_ = other.pool_;
        other.pool_ = nullptr;
    }
    BigMemoryPool(const BigMemoryPool& other) : pool_(new char[PoolSize])
    {
        std::cout << "copy big memory pool." << std::endl;
        memcpy(pool_, other.pool_, PoolSize);
    }
private:
    char *pool_;
};

BigMemoryPool get_pool(const BigMemoryPool& pool)
{
    return pool;//调用一次复制构造,这里是const左值,所以调用拷贝构造
}

BigMemoryPool make_pool()
{
    BigMemoryPool pool;
    return get_pool(pool);//调用一次移动构造,因为返回的是右值
}

int main(){
    BigMemoryPool my_pool = make_pool();//调用一次移动构造,因为返回的是右值
}

输出结果:
copy big memory pool.
move big memory pool.
move big memory pool.

可以看到减少了两次拷贝构造。

4.2 完美转发

这是一个常规的函数转发模板:

#include 
#include 
template<class T>
void show_type(T t)
{
	std::cout << typeid(t).name() << std::endl;
}
template<class T>
void normal_forwarding(T t)
{
	show_type(t);
}
int main()
{
	std::string s = "hello world";
	normal_forwarding(s);
}

其中转发函数按照值传递的方式进行转发,std::string在转发过程中会额外发生一次临时对象的复制。进行以下修改:

#include 
#include 
template<class T>
void show_type(T t)
{
	std::cout << typeid(t).name() << std::endl;
}
template<class T>
void perfect_forwarding(T &&t)
{
	show_type(static_cast<T&&>(t));
}
std::string get_string()
{
	return "hi world";
}
int main()
{
	std::string s = "hello world";
	perfect_forwarding(s);
	perfect_forwarding(get_string());
}

你可能感兴趣的:(c++,开发语言)