计算机 · 2021年5月22日 0

深入应用C++11笔记 第2章

使用C++11改进程序性能

右值引用

左值是指表达式结束后依然存在的持久对象,右值是表达式结束时就不再存在的临时对象。可以通过能否对表达式取地址来区分左值和右值。右值又分为将亡值(xvalue,expiringvalue)和纯右值(prvalue,PureRvalue)。纯右值包括分引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等;将亡值是C++11新增的,包括将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值。

因为右值不具名,所以只能通过引用的方式来找到它。而无论是声明左值引用还是右值引用,都必须立即进行初始化,因为引用类型本身并不拥有所绑定对象的内存。而右值引用的声明又使得该右值又“重获新生”,其生命周期变得与右值引用类型变量的生命周期一样,只要该变量还活着,该右值临时量就会一直存活下去。

作者举的例子:

   #include <iostream>
   using namespace std;

   int g_constructCount = 0;
   int g_copyConstructcount = 0;
   int g_destructCount = 0;

   struct A {
     A() { cout << "construct: " << ++g_constructCount << endl; }
     A(const A& a) { cout << "copy construct: " << ++g_copyConstructCount << endl; }
     ~A() { cout << "destruct: " << ++g_destructCount << endl; }
   }
   A GetA() { return A(); }
   int main() {
     A a = GetA();
     return 0;
   }

在关闭rvo优化(-fno-elide-constructors)的情况下,上述代码会调用三次构造函数,分别是GetA()函数内部生成A对象,GetA()函数生成返回值,生成对象a。如果把A a = GetA()改成A &&a = GetA(),那么就会少掉生成对象a这一次,因为现在a是GetA()函数返回值的引用了。改成const A& a = GetA()也可以达到这个效果,因为常量左值引用是一个”万能”的引用类型,可以接受左值、右值、常量左值和常量右值。但是改为普通的左值引用就不行了,会编译错误,普通的左值引用只接受左值。

T&&并不一定表示右值,在发生类型推导的时候T&&代表万能引用或者通用引用(universal references),可以是左值也可以是右值。发生类型推导的情景有模板和auto。注意一定要是发生类型推导时才是万能引用,否则只是普通的右值引用。只有T&&才可以表示universal references,任何附加条件如const universal references都会使其失效变成普通的右值引用。

引用折叠:

  • 所有的右值引用叠加到右值引用上仍然还是一个右值引用;
  • 所有的其他引用类型之间的叠加都将变成左值引用;

关于&&的总结:

  • 左值和右值是独立于它们的类型的,右值引用类型可能是左值也可能是右值;
  • auto&&或函数参数类型自动推导的T&&是一个未定的引用类型,被称为universal references,它可能是左值引用也可能是右值引用类型,取决于初始化的值类型;
  • 所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用折叠都为左值引用。当T&&为模板参数时,输入左值,它会变成左值引用,而输入右值时则变为具名的右值引用。
  • 编译器会将已命名的右值引用视为左值,而将未命名的右值引用视为右值。

现自定义的右值版本拷贝构造函数和赋值函数函数可以转移对象内存在堆上的资源,提高性能。

move语义

通过A& A::operator=(const A&&rhs)这样的move constructor或者std::move函数来转移对象的资源(内存、文件句柄等),避免不必要的深拷贝。

forward和完美转发

emplace_back减少内存拷贝和移动

需要注意如果原来的容器中就有相应的key,emplace_back不能更新值。

无序容器

通过unordered_mapunordered_multimapunordered_setunordered_multiset提供hash表。

对于自定义的key类型,需要实现key的hash函数和比较函数。