左值引用与右值引用在移动语义中的使用

左值引用与右值引用在移动语义中的使用

本文主要介绍C++中的左值引用和C++11中的新特性右值引用,以及右值引用在移动语义上的应用。

1、左值引用与右值引用

传统C++使用的是左值引用,使得标志符关联到左值。什么是左值呢?左值指的是一个表达数据的表达式,可以应用取地址操作符。常常出现在赋值操作符(=)的左边,所以被称为左值。给出如下定义:
左值引用:表示数据的表达式,如变量名或者解除引用的指针。

左值引用:
int n;//n是左值
int *pt = new int;//*pt是左值
const int b = 101;//b是左值
int &rn = n;//rn是左值
int &rt = *pt;//rt是左值
const int & rb = b;//rb是左值

右值引用:不能使用地址运算符操作的值。通常使用&&来声明右值引用。其中包括字面常量(C-风格字符串除外)、x+y、以及有返回值的函数(返回类型不是引用)。

右值引用
int x = 10;//10是右值
int y = 20;//20是右值
int &&r1 = 13;//r1与13都是右值
int &&r2 = x+y;//r2与x+y都是右值
double && r3 = std::sqrt(2.0);//r3是右值

PS:其中r2关联到x+y,指的是r2关联到x+y的结果的值,而不是x+y,如果x或者y发生变化,之后r2是不会发生变化的。

其中,左值转换为右值,或者右值转化为左值会有两种途径。

方法1int && x = static_cast<int &&>(n);
方法2#include<utility>
	four = std::move(one);//one赋值给four,调用了移动赋值运算符 

2、右值引用实现移动语义

什么是移动语义?C++11如何使用移动语义?
如果出现如下情况:

vector<string> bigdata;//含有200000个100000个字符的字符串
vector<string> bigdata_copy(bigdata);//一个新的对象作为bigdata的复制
vector<string> allcaps(const vector<string>& vs){
	vector<string> temp = vs;
	return temp;
} 
//调用函数allcaps
vector<string> bigdata_copy2(allcaps(bigdata));

会出现什么问题呢?发现temp这个变量管理着20000000000个字符串,vector与string的复制构造函数都会创建2000000000个字符的副本,而且函数也会创建临时变量作为返回值,结束后销毁。我们可以发现其中的大多数的复制与销毁操作时非常多余的。可以只修改一下这块内存的记录,将这块内存的记录赋值给另一个变量。如上改进的操作就被称为移动语义。

给出应用移动语义的例子:

#include <iostream>
using namespace std;

class Useless {
private:
	int n;
	char* pc;
	static int ct;
	void ShowObject() const;
public:
	Useless();
	explicit Useless(int k);
	Useless(int k,char ch);
	Useless(const Useless & f);
	Useless(Useless && f) noexcept;
	~Useless();
	Useless operator+(const Useless& f)const;
	void ShowData()const;
};

int Useless::ct = 0;

Useless::Useless() {
	++ct;
	n = 0;
	pc = nullptr;
	cout << "default constructor called;number of objects: " << ct << endl;
	ShowObject();
}

Useless::Useless(int k) :n(k) {
	++ct;
	cout << "int char constructor called; number of objects: " << ct << endl;
	pc = new char[n];
	ShowObject();
}

Useless::Useless(int k, char ch) :n(k) {
	++ct;
	cout << "int, char constructot called ;number of objects: " << ct << endl;
	pc = new char[n];
	for (int i = 0; i < n; i++) {
		pc[i] = ch;
	}
	ShowObject();
}

Useless::Useless(const Useless& f) :n(f.n) {
	++ct;
	cout << "copy const called; number of objects:" << ct << endl;
	pc = new char[n];
	for (int i = 0; i < n; i++) {
		pc[i] = f.pc[i];
	}
	ShowObject();
}

Useless::Useless(Useless&& f) noexcept :n(f.n) {
	++ct;
	cout << "move constrctor called; number of objects:" << ct << endl;
	pc = f.pc;
	f.pc = nullptr;
	f.n = 0;
	ShowObject();
}

Useless::~Useless() {
	cout << "destructor called; objects left: " << --ct << endl;
	cout << "deleted object:\n";
	ShowObject();
	delete[] pc;
}

Useless Useless::operator+(const Useless& f)const {
	cout << "Entering operator+()\n";
	Useless temp = Useless(n + f.n);
	for (int i = 0; i < n; i++)
		temp.pc[i] = pc[i];
	for (int i = n; i < temp.n; i++)
		temp.pc[i] = f.pc[i-n];
	cout << "temp object:\n";
	cout << "Leaving operator+()\n";
	return temp;
}

void Useless::ShowObject() const {
	cout << "Number of elements: " << n;
	cout << "Data address: " << (void*)pc << endl;
}

void Useless::ShowData() const {
	if (n == 0)
		cout << "(object empty)";
	else
		for (int i = 0; i < n; i++)
			cout << pc[i];
	cout << endl;
}

int main() {
	{
		Useless one(10, 'x');
		Useless two = one;
		Useless three(20,'o');
		Useless four(one+two);
		cout << "object one :";
		one.ShowData();
		cout << "object two :";
		two.ShowData();
		cout << "object three :";
		three.ShowData();
		cout << "object four :";
		four.ShowData();
	}
	return 0;
}

单独拿出上述例子中的移动构造函数:

Useless::Useless(Useless&& f) noexcept :n(f.n) {
	++ct;
	pc = f.pc;
	f.pc = nullptr;
	f.n = 0;
	ShowObject();
}

PS:其中需要注意的是,将对象f的pc赋值给this的pc时,需要将f.pc置为fullptr。因为如果不将f.pc指向fullptr,则f与this对象销毁的时候,会释放两次内存,发生错误。

在C++11中一般会声明移动复制构造函数与移动赋值运算符。也可以单独声明移动函数。

Useless::Useless(Useless&& f) noexcept :n(f.n) {
	++ct;
	pc = f.pc;
	f.pc = nullptr;
	f.n = 0;
	ShowObject();
	}
Useless& Useless::operator=(Useless&& f):n(f.n) {
	if(this == &f)
		return *this;
	delete[] pc;
	n = f.n;
	pc = f.pc;
	f.pc = fullptr;
	f.n = 0;
	return *this;
	}

编译器会根据参数的类型自动识别输入的是左值还是右值,调用相应的构造函数或者赋值操作符函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值