📚
cpp
  • README.md
  • Effective c++
    • 1.让自己习惯c++
    • 2.构造/析构/赋值运算
    • 3.资源管理
    • 4.设计与声明
    • 5.实现
    • 6.继承与面向对象设计
    • 7.模板与泛型编程
    • 8.定制new和delete
  • More effective c++
    • 1.基础议题
    • 2.操作符
    • 3.异常
    • 4.效率
    • 5.技术
    • 6.杂项讨论
  • effective modern c++
    • 1.型别推导
    • 2.auto
    • 3.转向现代c++
    • 4.智能指针
    • 5.右值引用,移动语义和完美转发
    • 6.lambda表达式
    • 7.并发API
    • 8.微调
  • stl源码剖析
  • cpp-concurrency-in-action
Powered by GitBook
On this page
  • 条款23:理解std::move和std::forward
  • 条款24:区分万能引用和右值引用
  • 条款25:针对右值使用std::move,针对万能引用使用std::forward
  • 条款26:避免对万能引用进行重载
  • 条款27:熟悉万能引用型别进行重载的替代方案
  • 条款29:理解引用折叠
  • 条款29:假定移动操作不存在,成本高,未使用
  • 条款30:了解完美转发失败的场景:

Was this helpful?

  1. effective modern c++

5.右值引用,移动语义和完美转发

首先把移动语义和完美转发概念列出:

移动语义使得编译器可以使用不太昂贵的移动操作来代替昂贵的复制操作。不仅如此,移动语义也使得创建只支持移动型别对象成为可能。这些型别包括,std::unique_ptr,std::future和std::thread。

完美转发使得程序员可以撰写接受任意实参的函数模板,并将其转发到其他函数,目标函数会接受到与转发函数所相同的实参

条款23:理解std::move和std::forward

这里主要理解几个要点:

  • std::move实施的是无条件的向右值型别的强制型别装换。就其本身而言,它不会执行移动操作。

  • 仅当传入的实参被绑定到右值的时候,std::forward才针对该实施向右型别的强制型别装换

  • 在运行期,std::move和std::forward是不会进行任何操作的。在编译器就会把转换做完。

条款24:区分万能引用和右值引用

万能引用和右值引用长得很像。都是形如type&&。

但是如果这个type是函数模板,切需要推导,这就是万能引用。因为能推导是左值还是右值。

//是万能引用
template<typename T>
void f(T&& param);

auto&& var2=var1;

//不是万能引用
template<typename T>
void f(std::vector<T>&& param);

template<typename T>
void f(const T&& param);


template<typename T>
class Vertor{
public:
    void push_back(T&& x);//不存在推导,不是万能引用

    template<typename... Args>
    void emplace_back(Args&&... args)//存在推导,是万能引用
}

条款25:针对右值使用std::move,针对万能引用使用std::forward

这个条款大概意思就是,如果返回值是一个右值,最后使用std::move将值移入返回值。如果参数是万能引用,最后是返回一个值,那最后要使用std::forward转发。

这个条款还讲解了返回值优化。如果参数不是引用,或者函数有中间变量。那么函数过后变量就要销毁。如果返回这个参数或者这个中间变量,使用移动构造函数而不是拷贝构造函数是不是更快呢。

确实有这个道理。但是,c++编译器已经帮你做好了返回值优化,这里不需要你去使用std::move,一般情况下,c++编译器会直接在返回值位置构造对象,其他情况,编译器会自行执行std::move,不需要程序员写

条款26:避免对万能引用进行重载

首先,对于一个接受string的函数。如果可以接受左值右值和字符串字面值,我们可以用const string&lrs参数来接受,但是,这会产生很多效率问题。

这时候,我们可以用万能引用,最后使用完美转发区分左右值。

但是需要注意的是,如果我们将这个函数重载。比如可以接受一个int类型。

一般,可以使用short类型作为实参带入给int类型中。但是使用万能引用重载的函数是贪婪的,这时候short和万能引用更加匹配。所以会产生意想不到的后果。

这个条款告诉我们,尽量避免对万能引用函数的重载。

条款27:熟悉万能引用型别进行重载的替代方案

这个条款我现在感觉用处不是很大,没有仔细升入了

条款29:理解引用折叠

首先需要知道的是,万能引用为什么能够识别出是左值还是右值。万能引用和右值引用的区别就是是否具有推导。

大概也能猜到结果了,左值右值的信息会被编码进推导结果中的。正是这个机制使得std::forward得以运作

双重指针是合法的,但是双重引用,确实违法的。这是对于代码而言,对于编译器而言,双重引用确是合法的。

template<typename T>
void f(T&& param);

f(w); //传入左值,T的型别推导会是T&

//会实例化为
f(Widget& && param);

//最后引用折叠,变为
f(Widget& param);

这样一来,就会有4中组合,都是引用:左左,左右,右右,右左。只有两个都是右的情况下,最后会被折叠成右值引用,其他都是左值引用。

引用折叠也是完美转发的实现原理

对于万能引用,传入左值,模板会被推导为T&,传入右值会被推导为T

最后说一下引用折叠会发生的4个语境:模板实例化,auto型别生成,创建和运用typedef和别名声明,以及decltype

条款29:假定移动操作不存在,成本高,未使用

这个条款在几个方面,告诉我们移动语义不会给你带来好处

  • 没有移动操作:没有提供移动操作,移动默认成复制

  • 移动未能更快:有移动,但是并不能更快

  • 移动不可用:有些操作要求移动操作不可发射异常

条款30:了解完美转发失败的场景:

这个条款就直接列要点了:

  • 完美转发失败的情形,是源于型别推导的失败,或推导结果是错误的型别

  • 会导致完美转发事变的实参种类有大括号初始化物,以及值0或者NULL表达的空指针,仅有声明的整形static const成员变量,模板或者重载函数的名字,位域

Previous4.智能指针Next6.lambda表达式

Last updated 5 years ago

Was this helpful?