左值引用:
左值引用使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或者解引用的指针),程序可获取其地址,最初左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符,即不能给他赋值(可以初始化),但可获取其地址:
int x=20;
int &rx1=x;//non-const引用可以被non-const左值初始化
const int y=10;
int &rx2=y;//非法,non-const引用不能被const左值初始化
int &rx3=10;// 非法:non-const引用不能被右值初始化
但是const引用限制就少了:
int x = 10;
const int cx = 20;
const int& rx1 = x; // const引用可以被non-const左值初始化
const int& rx2 = cx; // const引用可以被const左值初始化
const int& rx3 = 9; // const引用可以被右值初始化
特别注意的是const左值引用可以接收右值
右值引用:
C++11新增了右值引用,使用&&表示,右值引用可以关联到右值,即可出现在赋值表达式的右边,但不能对其应用地址运算符的值。右值包括字面常量(C风格字符串除外,它表示地址)、 诸如x+y等表达式以及返回值的函数(条件是该函数返回的不是引用)
右值引用一定不能被左值所初始化,只能用右值初始化:
因为右值引用的目的是为了延长用来初始化对象的生命周期,对于左值,其生命周期与其作用域有关,你没有必要去延长
int x=10;//左值
int y=20;//左值
int &&z=x;//非法:右值引用无法被左值初始化
const int &&k=y;//非法:右值引用无法被左值初始化
int &&r1=13;
int &&r2=x+y;//x+y是一个右值,r2延长其生命周期
double &&r3=std::sqrt(2.0);
cout<<"value:"<<r1<<" address:"<<&r1<<endl;
r2关联到的是当时计算x+y得到的结果。也就是所r2关联到的是30,即使后面修改了x和y,也不会影响r2;
将右值关联到右值引用导致该右值被存储在特定的位置,且可以获取该位置的地址。也就是说,虽然不能将运算符&用于13,但是可将其用于r1。通过数据与特定的地址关联,使得可以通过右值引用来访问该数据。
double up(double x) { return 2.0*x; }
void r1(const double&x) { cout << x << endl; }
void r2(double &x) { cout << x << endl; }
void r3(double &&x) { cout << x << endl; }
double w=3.14;
r1(w); //ok,r1 accept a lvalue
r1(w + 1); //ok,r1 accept a temp ,this temp inintial to w+1
r1(up(w)); //ok,r1 accept a temp ,this temp inintial to return value of up(w)
r2(w); //ok,r1 accept a lvalue
r2(w + 1); //error ,w+1 is a rvalue
r2(up(w)); //error ,w+1 is a rvlaue
r3(w); //error ,w+1 is a lvlaue
r3(w + 1); //ok,r3 accept a temp that copy of w+1
r3(up(w)); //ok,ok,r3 accept a temp that copy of return value of up(w)
编译器错误提示:
移动构造函数:
在某些场景下,使用一个对象初始化另一个对象,可以通过复制构造函数进行深复制完成,但有时候用于的旧对象不再使用,可以通过移动构造函数提高效率。移动构造函数只调整记录,是将所有权转移到新对象的过程,这个过程中可能修改其实参,这意味着右值引用参数不应是const。通俗的将就类似于在计算机中移动文件的情形:实际文件还留在原来的地方,而只修改了记录,这就是移动语义。
实例代码:
//Useless.h
#ifndef USELESS_H_
#define USELESS_H_
class Useless
{
private:
int n;
char*p;
static int ct;
void showobject();
public:
Useless();
explicit Useless(int k);
Useless(int k, char c);
Useless(const Useless&ul);
Useless(Useless&&ul);
Useless& operator=(const Useless&ul);
Useless& operator=(Useless&&ul);
Useless operator+(const Useless&ul);
~Useless();
void ShowData()const;
};
#endif
//Useless.cpp
#include "Useless.h"
#include
int Useless::ct = 0;
void Useless::showobject()
{
std::cout << "numbers of object:" << n << std::endl;
std::cout << "address of data:" << (void*)p << std::endl;
}
Useless::Useless():n(0),p(nullptr)
{
++ct;
std::cout << "default constructor called;numbers ofobject:" << ct << std::endl;
showobject();
}
Useless::Useless(int k):n(n=k)
{
++ct;
std::cout << "int constructor called;numbers ofobject:" << ct << std::endl;
p = new char[k];
showobject();
}
Useless::Useless(int k, char c):n(k)
{
++ct;
std::cout << "int char constructor called;numbers ofobject:" << ct << std::endl;
p = new char[n];
for (int i = 0; i < n; i++)
p[i] = c;
showobject();
}
Useless::Useless(const Useless & ul)
{
++ct;
std::cout << "copy const called;numbers ofobject:" << ct << std::endl;
n = ul.n;
p = new char[n];
for (int i = 0; i < n; i++)
p[i] = ul.p[i];
showobject();
}
Useless::Useless(Useless && ul)
{
++ct;
std::cout << "move constructor called;numbers ofobject:" << ct << std::endl;
n = ul.n;
p = ul.p;
ul.n = 0;
ul.p = nullptr;
showobject();
}
Useless& Useless::operator=(const Useless & ul)
{
if (this == &ul)
return *this;
delete[]p;
n = ul.n;
p = new char[n];
for (int i = 0; i < n; i++)
p[i] = ul.p[i];
return *this;
}
Useless& Useless::operator=(Useless && ul)
{
if (this == &ul)
return *this;
delete[]p;
n = ul.n;
p = ul.p;
ul.n = 0;
ul.p = nullptr;
return *this;
}
Useless Useless::operator+(const Useless & ul)
{
std::cout << "enter operator +:" << std::endl;
Useless u(n + ul.n);
u.p = new char[n + ul.n];
for (int i = 0; i < n; i++)
u.p[i] = p[i];
for(int i=n;i<n+ul.n;i++)
u.p[i] = ul.p[i-n];
std::cout << "temp object:" << std::endl;
std::cout << "left operator +:" << std::endl;
return u;
}
Useless::~Useless()
{
--ct;
std::cout << "destructor called;numbers of object left:" << ct << std::endl;
std::cout << "delete object:" << std::endl;
showobject();
delete[] p;
}
void Useless::ShowData()const
{
if (n == 0)
std::cout << "object empty" << std::endl;
else
for (int i = 0; i < n; i++)
std::cout << p[i];
std::cout<< std::endl;
}
//main.cpp
#include"Useless.h"
#include
using namespace std;
void main()
{
{
Useless one(10, 'q');
Useless two = one;//call copy constructor
Useless three(10, 'w');
Useless four(one + three);//call move constructor
cout << "object one:" << endl;
one.ShowData();
cout << "object two:" << endl;
two.ShowData();
cout << "object three:" << endl;
three.ShowData();
cout << "object four:" << endl;
four.ShowData();
}
cout << "------------" << endl;
{
Useless one(10, 'x');
Useless two = one + one;//call move constructor
cout << "object one:" << endl;
one.ShowData();
cout << "object two:" << endl;
two.ShowData();
Useless three, four;
cout << "three =one" << endl;
three = one;//automatic copy assignment
cout << "object three:" << endl;
three.ShowData();
cout << "object one:" << endl;
one.ShowData();
cout << "four=one+two" << endl;
four = one + two;//automatic move assignment
cout << "object four:" << endl;
four.ShowData();
cout << "four=move(one)" << endl;
four = move(one);//forced move assignment
cout << "object four:" << endl;
four.ShowData();
cout << "object one:" << endl;
one.ShowData();
}
}
对比复制构造函数和移动构造函数:
Useless::Useless(const Useless & ul)
{
++ct;
n = ul.n;
p = new char[n];
for (int i = 0; i < n; i++)
p[i] = ul.p[i];
}
Useless::Useless(Useless && ul)
{
++ct;
n = ul.n;
p = ul.p;
ul.n = 0;
ul.p = nullptr;
}
下面的语句将使用复制构造函数:
Useless two = one;//call copy constructor
引用ul将指向左值one。
Useless four(one + three);//call move constructor
这条语句表达式one + three调用operator+,而右值引用ul将关联到该方法返回的临时对象。这样对象four存储数据的地址和operator+()中创建的对象的数据地址是一样的,对象four创建完成后,为临时对象调用析构函数。
而在移动构造函数中,p指向现有的数据,以获取这些数据的所有权。这是p和ul.p指向相同的数据,调用析构函数是将出现问题,因为程序不能对同一个地址调用两次delete[]。为了避免这样的问题,该构造函数随后将原来的指针设置为空指针,对空指针执行delete[]是没有问题的,由于要修改ul对象,因此不能声明称const。
何时会调用移动构造函数?
Useless two = one;//match Useless::Useless(const Useless & ul)
Useless four(one + three);//match Useless::Useless( Useless && ul)
对象one是左值,与左值引用匹配。而表达式one + three是右值,与右值引用匹配,因此编译器使用移动构造函数。
如果没有定义移动沟站函数,第二句将调用复制构造函数,但是左值不能指向右值。编译器的处理是如果实参是一个右值,const引用形参将指向一个临时变量。因此形参ul将被初始化为一个临时对象,该临时对象被初始化为operator+()返回的值。整个过程将创建3个对象,但其中两个被删除。因此使用移动语义消除了额外的工作。
移动赋值函数:
和移动构造函数类似:
//copy assignment
Useless& Useless::operator=(const Useless & ul)
{
if (this == &ul)
return *this;
delete[]p;
n = ul.n;
p = new char[n];
for (int i = 0; i < n; i++)
p[i] = ul.p[i];
return *this;
}
//move assignment
Useless& Useless::operator=(Useless && ul)
{
if (this == &ul)
return *this;
delete[]p;
n = ul.n;
p = ul.p;
ul.n = 0;
ul.p = nullptr;
return *this;
}
关于强制移动:
移动构造函数和移动赋值函数使用右值。如果让他们使用左值,该怎么处理?
通过使用到std::move()函数实现:
Useless three;
four = move(one);//forced move assignment
std::move函数内部到底是怎么实现的。其实std::move函数并不“移动”,它仅仅进行了类型转换,std::move函数只是简单地调用static_cast将参数转化为右值引用,诉编译器将传入的参数无条件地转化为一个右值。
特殊的成员函数:
在原有的4个特殊成员函数(构造函数、复制构造函数、赋值运算符、和析构函数)的基础上,c++11新增了两个:移动构造函数和移动赋值运算符。
在没有定义任何函数的时候,编译器将提供默认的上述6个函数(如果代码中使用到)。
如果提供了析构函数、复制构造函数或者复制赋值运算符,编译器将不会自动提供移动构造函数和移动赋值运算符;如果提供了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和复制赋值函数。
参考:
https://zhuanlan.zhihu.com/p/54050093