Constructor is called! this->_data: 29483024
  // middle对象的构造函数
  Copy Constructor is called! src: 29483024 dst: 29483056
  // 临时对象的构造,通过middle对象调用复制构造函数
  DeConstructor is called! this->_data: 29483024
  // middle对象的析构
  Copy Constructor is called! src: 29483056 dst: 29483024
  // a对象构造,通过临时对象调用复制构造函数
  DeConstructor is called! this->_data: 29483056
  // 临时对象析构
  DeConstructor is called! this->_data: 29483024
  // a对象析构
  在上述例子中,临时对象的构造、复制和析构操作所带来的效率影响一直是C++中为人诟病的问题,临时对象的构造和析构操作均对堆上的内存进行操作,而如果_data的内存过大,势必会非常影响效率。从程序员的角度而言,该临时对象是透明的。而这一问题正是C++11中需要解决的问题。
  在C++11中解决该问题的思路为,引入了移动构造函数,移动构造函数的定义如下。
  MyString(MyString &&str) {
  cout << "Move Constructor is called! src: " << (long)str._data << endl;
  _len = str._len;
  _data = str._data;
  str._data = nullptr;
  }
  在移动构造函数中我们窃取了str对象已经申请的内存,将其拿为己用,并将str申请的内存给赋值为nullptr。移动构造函数和复制构造函数的不同之处在于移动构造函数的参数使用&&,这是下文要讲解的右值引用符号。参数不再是const,因为在移动构造函数需要修改右值str的内容。
  移动构造函数的调用时机为用来构造临时变量和用临时变量来构造对象的时候移动语义会被调用。可以通过下面的输出结果看到,我们所使用的编译参数为g++ test.cpp -o main -g -fno-elide-constructors –std=c++11。
  Constructor is called! this->_data: 22872080
  // middle对象构造
  Move Constructor is called! src: 22872080
  // 临时对象通过移动构造函数构造,将middle申请的内存窃取
  DeConstructor is called!
  // middle对象析构
  Move Constructor is called! src: 22872080
  // 对象a通过移动构造函数构造,将临时对象的内存窃取
  DeConstructor is called!
  // 临时对象析构
  DeConstructor is called! this->_data: 22872080
  // 对象a析构
  通过输出结果可以看出,整个过程中仅申请了一块内存,这也正好符合我们的要求了。
  C++98中的左值和右值
  我们先来看下C++98中的左值和右值的概念。左值和右值直观的理解是一条语句等号左边的为左值,等号右边的为右值,而事实上该种理解是错误的。左值:可以取地址,有名字的值,是一个指向某内存空间的表达式,可以使用&操作符获取内存地址。右值:不能取地址,即非左值的都是右值,没有名字的值,是一个临时值,表达式结束后右值没有意义了。我想通过下面的例子,读者可以清楚的理解左值和右值了。
// lvalues:
//
int i = 42;
i = 43;
// i是左值
int* p = &i;
// i是左值
int& foo();
foo() = 42;
// foo()返回引用类型是左值
int* p1 = &foo();
// foo()可以取地址是左值
// rvalues:
//
int foobar();
int j = 0;
j = foobar();
// foobar()是右值
int* p2 = &foobar();
// 编译错误,foobar()是右值不能取地址
j = 42;
// 42是右值
  C++11右值引用和移动语义
  在C++98中有引用的概念,对于const int &m = 1,其中m为引用类型,可以对其取地址,故为左值。在C++11中,引入了右值引用的概念,使用&&来表示。在引入了右值引用后,在函数重载时可以根据是左值引用还是右值引用来区分。
void fun(MyString &str)
{
cout << "left reference" << endl;
}
void fun(MyString &&str)
{
cout << "right reference" << endl;
}
int main() {
MyString a("456");
fun(a);
// 左值引用,调用void fun(MyString &str)
fun(foo());
// 右值引用,调用void fun(MyString &&str)
return 1;
}
  在绝大多数情况下,这种通过左值引用和右值引用重载函数的方式仅会在类的构造函数和赋值操作符中出现,被例子仅是为了方便采用函数的形式,该种形式的函数用到的比较少。上述代码中所使用的将资源从一个对象到另外一个对象之间的转移是移动语义。这里提到的资源是指类中的在堆上申请的内存、文件描述符等资源。
  前面已经介绍过了移动构造函数的具体形式和使用情况,这里对移动赋值操作符的定义再说明一下,并将main函数的内容也一起更改,将得到如下输出结果。
MyString& operator=(MyString&& str) {
cout << "Move Operator= is called! src: " << (long)str._data << endl;
if (this != &str) {
if (_data != nullptr)
{
free(_data);
}
_len = str._len;
_data = str._data;
str._len = 0;
str._data = nullptr;
}
return *this;
}
int main() {
MyString b;
b = foo();
return 1;
}
// 输出结果,整个过程仅申请了一个内存地址
Constructor is called!
// 对象b构造函数调用
Constructor is called! this->_data: 14835728
// middle对象构造
Move Constructor is called! src: 14835728
// 临时对象通过移动构造函数由middle对象构造
DeConstructor is called!
// middle对象析构
Move Operator= is called! src: 14835728
// 对象b通过移动赋值操作符由临时对象赋值
DeConstructor is called!
// 临时对象析构
DeConstructor is called! this->_data: 14835728
// 对象b析构函数调用
  在C++中对一个变量可以通过const来修饰,而const和引用是对变量约束的两种方式,为并行存在,相互独立。因此,可以划分为了const左值引用、非const左值引用、const右值引用和非const右值引用四种类型。其中左值引用的绑定规则和C++98中是一致的。
  非const左值引用只能绑定到非const左值,不能绑定到const右值、非const右值和const左值。这一点可以通过const关键字的语义来判断。
  const左值引用可以绑定到任何类型,包括const左值、非const左值、const右值和非const右值,属于引用类型。其中绑定const右值的规则比较少见,但是语法上是可行的,比如const int &a = 1,只是我们一般都会直接使用int &a = 1了。
  非const右值引用不能绑定到任何左值和const右值,只能绑定非const右值。
  const右值引用类型仅是为了语法的完整性而设计的, 比如可以使用const MyString &&right_ref = foo(),但是右值引用类型的引入主要是为了移动语义,而移动语义需要右值引用是可以被修改的,因此const右值引用类型没有实际意义。
  我们通过表格的形式对上文中提到的四种引用类型可以绑定的类型进行总结。
  引用类型/是否绑定 非const左值 const左值 非const右值 const右值 备注
  非const左值引用 是 否 否 否 无
  const左值引用 是 是 是 是 全能绑定类型,绑定到const右值的情况比较少见
  非const右值引用 否 否 是 否 C++11中引入的特性,用于移动语义和完美转发
  const值引用 是 否 否 否 没有实际意义,为了语法完整性而存在
  下面针对上述例子,我们看一下foo函数绑定参数的情况。
  如果只实现了void foo(MyString &str),而没有实现void fun(MyString &&str),则和之前一样foo函数的实参只能是非const左值。
  如果只实现了void foo(const MyString &str),而没有实现void fun(MyString &&str),则和之前一样foo函数的参数即可以是左值又可以是右值,因为const左值引用是绑定类型。
  如果只实现了void foo(MyString &&str),而没有实现void fun(MyString &str),则foo函数的参数只能是非const右值。
  强制移动语义std::move()
  前文中我们通过右值引用给类增加移动构造函数和移动赋值操作符已经解决了函数返回类对象效率低下的问题。那么还有什么问题没有解决呢?
  在C++98中的swap函数的实现形式如下,在该函数中我们可以看到整个函数中的变量a、b、c均为左值,无法直接使用前面移动语义。
  template <class T>
  void swap ( T& a, T& b )
  {
  T c(a);
  a=b;
  b=c;
  }
  但是如果该函数中能够使用移动语义是非常合适的,仅是为了交换两个变量,却要反复申请和释放资源。按照前面的知识变量c不可能为非const右值引用,因为变量a为非const左值,非const右值引用不能绑定到任何左值。
  在C++11的标准库中引入了std::move()函数来解决该问题,该函数的作用为将其参数转换为右值。在C++11中的swap函数可以更改为了:
  template <class T>
  void swap (T& a, T& b)
  {
  T c(std::move(a));
  a=std::move(b);
  b=std::move(c);
  }
  在使用了move语义以后,swap函数的效率会大大提升,我们更改main函数后测试如下:
int main() {
// move函数
MyString d("123");
MyString e("456");
swap(d, e);
return 1;
}
// 输出结果,通过输出结果可以看出对象交换是成功的
Constructor is called! this->_data: 38469648
// 对象d构造
Constructor is called! this->_data: 38469680
// 对象e构造
Move Constructor is called! src: 38469648
// swap函数中的对象c通过移动构造函数构造
Move Operator= is called! src: 38469680
// swap函数中的对象a通过移动赋值操作符赋值
Move Operator= is called! src: 38469648
// swap函数中的对象b通过移动赋值操作符赋值
DeConstructor is called!
// swap函数中的对象c析构
DeConstructor is called! this->_data: 38469648
// 对象e析构
DeConstructor is called! this->_data: 38469680
// 对象d析构