左值引用与右值引用在移动语义中的使用
本文主要介绍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是不会发生变化的。
其中,左值转换为右值,或者右值转化为左值会有两种途径。
方法1:
int && 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;
}
编译器会根据参数的类型自动识别输入的是左值还是右值,调用相应的构造函数或者赋值操作符函数。