左值和右值

1. 左值和右值

1.1 什么是左值和右值?

在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。而右值则是不指向稳定内存地址匿名值(不具名对象),它的生命周期很短,通常是暂时性的。
基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。

1.2 复杂情况

x++;
++x;

x++和++x虽然都是自增操作,但是却分为不同的左右值。其中x++是右值,因为在后置++操作中编译器首先会生成一份x值的临时复制,然后才对x递增,最后返回临时复制内容。而++x则不同,它是直接对x递增后马上返回其自身,所以++x是一个左值。如果对它们实施取地址操作,就会发现++x的取地址操作可以编译成功,而对x++取地址则会报错

通常字面量都是一个右值,除字符串字面量以外.

2. 左值引用

非常量左值的引用对象很单纯,它们必须是一个左值。对于这一点,常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值。

常量左值引用可以引用右值的这个特性在函数形参列表中有着巨大的作用。一个典型的例子就是复制构造函数和赋值运算符函数

class X {
public:
	X() {}
	X(const X&) {}
	X& operator = (const X&) { return *this; }
};

X make_x()
{
	return X();
}

int main()
{
	X x1;
	X x2(x1);
	X x3(make_x());
	x3 = make_x();

	return 0;
}

如果这里将类X的复制构造函数和赋值运算符函数形参类型的常量性删除,则X x3(make_x());x3 = make_x();这两句代码会编译报错,因为非常量左值引用无法绑定到make_x()产生的右值**。常量左值引用可以绑定右值是一条非常棒的特性,但是它也存在一个很大的缺点——常量性。一旦使用了常量左值引用,就表示我们无法在函数内修改该对象的内容(强制类型转换除外)。所以需要另外一个特性来帮助我们完成这项工作,它就是右值引用。

3. 右值引用

右值引用是一种引用右值且只能引用右值的方法。
右值引用的特点之一就是可以延长右值的声明周期。

# include <iostream>

class X {
public:
	X() { std::cout << "X ctor" << std::endl; }
	X(const X& x) { std::cout << "X copy ctor" << std::endl; }
	~X() { std::cout << "X dtor" << std::endl; }
	void show() { std::cout << "show X" << std::endl; }
};

X make_x()
{
	X x1;
	return x1;
}

int main()
{
	X x2 = make_x();    //pos 1
	X&& x2 = make_x();  //pos  2 
	x2.show();
}

在g++编译器下pos 1的代码会发生三次构造,而pos 2仅发生两次构造。最后一次赋值运算符函数没有调用,而是直接引用了make_x()返回的临时对象。
延长临时对象生命周期并不是这里右值引用的最终目标,其真实目标应该是减少对象复制,提升程序性能。

#include <iostream>
#include <string.h>


class BigMemoryPool {
public:
    static const int PoolSize = 4096;
    BigMemoryPool() : pool_(new char[PoolSize]) {
        std::cout << "gouzao..\n";
    }
    ~BigMemoryPool()
    {
        if (pool_ != nullptr) {
            delete[] pool_;
        }
    }
    //拷贝构造函数
    BigMemoryPool(const BigMemoryPool& other) : pool_(new char[PoolSize])
    {
        if (&other == this) return;
        std::cout << "copy big memory pool." << std::endl;
        memcpy(pool_, other.pool_, PoolSize);
    }
    //移动构造函数
    BigMemoryPool(BigMemoryPool&& other) noexcept {
        std::cout << "move big memory pool" << std::endl;
        pool_ = other.pool_;
        other.pool_ = nullptr;
    }

    //移动赋值运算符
    BigMemoryPool& operator=(BigMemoryPool&& other) noexcept
    {
        std::cout << "move(operator=) big memory pool." << std::endl;
        if (pool_ != nullptr) {
                delete[] pool_;
        }
        pool_ = other.pool_;
        other.pool_ = nullptr;
        return *this;
    }


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

    return 0;
}

4. 值类别

C++11新引入的概念。值类别是表达式的一种属性,性,该属性将表达式分为3个类别,它们分别是左值(lvalue)、纯右值(prvalue)和将亡值(xvalue)。
在这里插入图片描述

5. 将左值转换为右值

void f1(int&& index){}
//index是一个右值引用,但它是一个左值
void f2(int&& index)
{
    f1(index);
}

6. 万能引用

void foo(int &&i) {}    // i为右值引用

template<class T>
void bar(T &&t) {}        // t为万能引用

int get_val() { return 5; }
int &&x = get_val();      // x为右值引用
auto &&y = get_val();     // y为万能引用

所谓的万能引用是因为发生了类型推导,在T&&和auto&&的初始化过程中都会发生类型的推导。不过无论如何都会是一个引用类型。

C++11添加了一套引用叠加推导的规则-引用折叠
在这里插入图片描述
只要有左值引用参与进来,最后推导的结果就是一个左值引用。只有实际类型是一个非引用类型或者右值引用类型时,最后推导出来的才是一个右值引用。

#include <vector>

template<class T>
void foo(std::vector<T>&& t) {}

void foo(int&& i) {}    // i为右值引用

template<class T>
void bar(T&& t) {}        // t为万能引用

int get_val() { return 5; }


int main()
{
	int i = 42;
	const int j = 11;
	bar(i);        //int&
	bar(j);			//const int&
	bar(get_val());//int &&

	int&& x = get_val();      // x为右值引用
	auto&& y = get_val();     // y为万能引用

	return 0;
}

7. 完美转发

只有在函数模板中使用 T&& 时,才需要使用 std::forward 进行完美转发;
让转发将左右值的属性也带到目标函数中

#include <iostream>
#include <string>

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(std::forward<T>(t));
}
//为了让转发将左右值的属性也带到目标函数中
std::string get_string()
{
	return "hi hello";
}

int main()
{
	std::string s = "hello world";
	normal_forwarding(s);
	normal_forwarding(get_string());
}
//发生一次代码拷贝, 改为void normal_forwarding(T& t)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值