c++ 11相关

  1. lambda表达式原理

    编译器会把一个lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符。

  2. Smart Pointers 智能指针

    unique_ptr: 如果内存资源的所有权不需要共享,就应当使用这个(它没有拷贝构造函数),但是它可以转让给另一个unique_ptr(存在move构造函数)。

    unique_ptr无法进行复制构造和赋值操作

    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2(pInt);    // 报错
    unique_ptr<int> pInt3 = pInt;   // 报错

    可以进行移动构造和移动赋值操作

    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2 = std::move(pInt);    // 转移所有权
    //cout << *pInt << endl; // 出错,pInt为空
    cout << *pInt2 << endl;
    unique_ptr<int> pInt3(std::move(pInt2));

    可以返回unique_ptr,unique_ptr不支持拷贝操作,但却有一个例外:可以从函数中返回一个unique_ptr。

    unique_ptr<int> clone(int p){
       unique_ptr<int> pInt(new int(p));
       return pInt;    // 返回unique_ptr
    }
    int main() {
       unique_ptr<int> ret = clone(5);
       cout << *ret << endl;
    }

    unique_ptr使用场景

    1、为动态申请的资源提供异常安全保证。
    2、返回函数内动态申请资源的所有权。
    3、在容器中保存指针。
    4、管理动态数组。
    5、作为auto_ptr的替代品。

    shared_ptr: 如果内存资源需要共享,那么使用这个(所以叫这个名字)。

    多线程无保护读写 shared_ptr 可能出现的 race condition,让我们考虑如下的一个场景:
    1)有 3 个 shared_ptr 对象 x、g、n。

    扫描二维码关注公众号,回复: 1645727 查看本文章

    shared_ptr g(new Foo); // 线程之间共享的 shared_ptr
    shared_ptr x; // 线程 A 的局部变量
    shared_ptr n(new Foo); // 线程 B 的局部变量

    2)初始时,一切正常。

    3)线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。

    4)同时线程 B 执行 g = n; (即 write g),两个步骤一起完成了,此时,Foo1 对象已经销毁,x.ptr 成了空悬指针!


    5)回到线程 A,完成步骤 2。

    多线程无保护地读写 g,造成了“x 是空悬指针”的后果,这正是多线程读写同一个 shared_ptr 必须加锁的原因。

    weak_ptr: 持有被shared_ptr所管理对象的引用,但是不会改变引用计数值。它被用来打破依赖循环。

  3. 左值、右值

    左值
    能够用&取地址的表达式是左值表达式。

    1. 函数名和变量名(实际上是函数指针③和具名变量,具名变量如std::cin、std::endl等)。
    2. 返回左值引用的函数调用、前置自增/自减运算符连接的表达式++i/–i。
    3. 由赋值运算符或复合赋值运算符连接的表达式(a=b、a+=b、a%=b)。
    4. 解引用表达式*p、字符串字面值”abc”。

    右值
    1)本身就是赤裸裸的、纯粹的字面值,如3、false;
    2)求值结果相当于字面值或是一个不具名的临时对象。

    1. ++i是左值,i++是右值。
      对于++i,对i加1后再赋给i,最终的返回值就是i,所以,++i的结果是具名的,名字就是i;而对于i++而言,是先对i进行一次拷贝,将得到的副本作为返回结果,然后再对i加1,由于i++的结果是对i加1前i的一份拷贝,所以它是不具名的。
    2. 解引用表达式*p是左值,取地址表达式&a是右值。
      &(*p)一定是正确的,因为*p得到的是p指向的实体,&(*p)得到的就是这一实体的地址,正是p的值。由于&(*p)的正确,所以*p是左值。而对&a而言,得到的是a的地址,相当于unsigned int型的字面值,所以是纯右值。
    3. a+b、a&&b、a==b都是纯右值。
      a+b得到的是不具名的临时对象,而a&&b和a==b的结果非true即false,相当于字面值。

    参考文档

    value_category

  4. std::move、std::forward

    背景知识

    1. 引用折叠规则:
      X& + & => X&
      X&& + & => X&
      X& + && => X&
      X&& + && => X&&
    2. 函数模板参数推导规则(右值引用参数部分):
      当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
      若实参为左值 U& ,则模板参数 T 应推导为引用类型 U& 。(根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
      若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U 。(根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U )
    3. std::remove_reference为C++0x标准库函数,功能为去除类型中引用(≡ 含义为“即,等价于“)。
      std::remove_reference<U&>::type ≡ U
      std::remove_reference<U&&>::type ≡ U
      std::remove_reference<U>::type ≡ U
    4. 以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)
      static_cast<T&&>(t)
      无名的右值引用是右值,具名的右值引用是左值。

    std::move

    std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语义问题。

    std::forward

    std::forward(u) 有两个参数:T 与 u。当T为左值引用类型时,u将被转换为T类型的左值,否则u将被转换为T类型右值。如此定义std::forward是为了在使用右值引用参数的函数模板中解决参数的完美转发问题。

猜你喜欢

转载自blog.csdn.net/shangshengshi/article/details/78508151