一、类型推导

PROs:

  • 源码某处的类型修改,可以自动传播其他地方

Cons:

  • 会让代码更复杂(How?)

    • 在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略

      template<typename T>
      void f(T & param); // param 是一个引用 int x = 10; // int
      const int cx= x; // const int
      const int &rx = cx; // const int & f(x); // T = int, param 类型为 int&
      f(cx); // T = const int, param 类型为 const int&, 常量性被保留
      f(rx); // T = const int, param 类型为 const int&, 引用被忽略
    • 对于通用引用的推导,左值实参会被特殊对待(T都被推导为了左值引用

      template<typename T>
      void f(T && param); // 通用类型引用,两个& f(x); // x 为左值,T = int&, param 类型为 int&
      f(cx); // cx 为左值, T = const int&, param 类型为 const int&
      f(rx); // rx 为左值, T = const int&, param 类型为 const int&
      f(10); // 10 为右值, T = int, param 类型为 int&&
    • 对于传值类型推导,实参如果具有常量性const和易变性volitate会被忽略

      template<typename T>
      void f(T param); // param 是一份拷贝 f(x); // T = param = int
      f(cx); // T = param = int,const 被忽略了,因为 const 不会(也不应该)传播到拷贝上
      f(rx); // T = param = int,const 被忽略了,因为 const 不会(也不应该)传播到拷贝上 const char* const ptr = "const string"; // ptr 为常量指针,且指向常量

    f(ptr); // T = char, param = char const,仅指针的常量性被忽略了


    + 在模板类型推导时,数组或者函数实参会**退化为指针**,除非他们被用于初始化引用 ```cpp
    const char name[] = "I am John."; // name 为 const char[11]
    const char* pname = name; // 数组退化为指针 template<typename T>
    void f1(T param); f1(name); // T = const char*,这里的常量性不会被移除么? template<typename T>
    void f2(T& param); f2(name); // T = const char[11], param 类型为 const char(&)[11] // 用途:编译器推导数组大小常量
    template<typename T, std::size_t N>
    constexpr std::size_t arraySize(T(&)[N]) noexcept {
    return N;
    } void bar(int, float); f1(bar); // param 的类型为: void(*)(int, float)
    f2(bar); // param 的类型为: void(&)(int, float)

二、move和forward

要点:当传递给函数我右值引用时,应该无条件转换为右值(使用std::move);通用引用应该有条件转换为右值(使用std::forward

举个栗子:

class Widget {
public:
// rhs 为一个右值引用,使用std::move
Widget(Widget&& rhs): name(std::move(rhs.name)), p(std::move(rhs.p)) {} template<typename T>
void setName(T&& newName){ // newName是一个通用引用
name = std::forward<T>(newName); // 使用std::forward
}
private:
std::string name;
std::shared_ptr<Data> p;
};

在上述栗子中,通用引用可能绑定到有资格移动的对象上。当使用右值初始化时,std::forward才会将其强制转化为右值

扩展场景一:

在使用按值返回的函数,且返回值绑定到右值引用或通用引用,需要对返回值使用std::movestd::forward

// Case 1: std::move
// 左侧的参数也用于保存计算的结果
Matrix operator+(Matrix&& lhs, const Matrix& rhs){
lhs += rhs;
return std::move(lhs); //lhs 可以移动到返回值时的内存位置
// return lhs; 会被编译器copy到返回值的内存空间
} // Case2: std::forward
template<typename T>
Fraction reduceAndCopy(T&& frac){
frac.reduce();
/*
*1. 如果 frac 是一个右值,则会直接移动到返回值中,避免copy开销
*2. 如果 frac 是一个左值,则必须创建副本
*/
return std::forward<T>(frac);
// return frac; // 总是会创建副本
}

扩展场景二:

开发者可能会误用 std::move,比如下面的栗子。

Widget makeWidget(){
Widget w;
...;
return w; // 编译器会自动进行RVO(返回值优化)
//return std::move(w); // 不要这样做!
}

RVO(返回值优化)的条件:

  1. 局部变量与返回值的类型相同
  2. 局部变量就是返回值
/*
* 1. 此处返回的已经不是局部对象 w,而是w的引用
* 2. 返回 w 的引用不符合RVO的第二个条件
* 3. 试图帮助编译器优化,反而限制了优化效果
*/
return std::move(w);

三、熟悉通用引用重载的替代方法

方式一:Pass by Value

将按引用传递参数替换为按值传递(这违反直觉)。

class Person{
public:
// 替换原生的 T&&
explicit Person(std::string s): name(std::move(s)){}
explicit Person(int idx): name(nameFromIdx(idx)){}
private:
std::string name;
};

方式二:使用Tag dispatch

首先回顾一下存在重载问题的case代码:

std::multiset<std::string> names;
template<typename T>
void logAndAdd(T&& name)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
// 当 name 是一个int类型的重载,则会导致names.emplace(int)错误
names.emplace(std::forward<T>(name));
}

解决方案:

template<typename T>
void logAndAdd(T&& name)
{
// 借助一个 bool tag实现重载函数的分发
logAndAddImpl(std::forward<T>(name),
std::is_integeral<typename std::remove_reference<T>::type>());
// 不直接使用std::is_integeral<T>()的原因是:is_inteageral<T&>会返回FALSE,对应传入左值的情况
} template<typename T>
void logAndAddImpl(T&& name, std::false_type)
{
names.emplace(std::forward<T>(name));
} // 特化版本实现
std::string nameFromIdx(int idx);
void logAndAdd(int idx, std::true_type)
{
logAndAdd(nameFromIdx(idx));
}

方法三:约束使用通用引用模板

要点:搭配使用 std::enable_ifstd::decaystd::is_base_of

class Person
{
public:
// 模板应该在 T 不是Person的时候启用
template<typename T, typename=std::enable_if<!std::is_base_of<Person, std::decay<T>>::value>>
explicit Person(T&& p);
};

使用 std::enable_if来选择性禁止Person通用引用构造器,以使得一些参数确保使用移动或copy构造函数。

四、引用折叠

引用折叠发生在四种情况:

  • 模板实例化
  • auto变量的类型生成
  • typedef
  • decltype
Widget w;
// w1为一个左值引用,auto推导出来的为 Widget&
// 代入即为:Widget& && w1 = w; 引用折叠后为 Widget& w1 = w;
auto&& w1 = w;

通用引用不是一种新的引用,它实际上是满足两个条件下的右值引用:

  • 通过类型推导将左值和右值区分。

    • T 类型的左值被推导为&类型,T类型的右值被推导为T
  • 引用折叠的发生

typedef的样例:

template<typename T>
class Widget
{
public:
typedef T&& RvalueRefToT;
}; Widget<int&> w;
/*推导-->*/ typedef int& && RvalueRefToT;
/*引用折叠-->*/ typedef int& RvalueRefToT;

这清楚地表明 typedef 有时可能并不按照我们预期的设置生效的。

五、完美转发的失败case

std::forward只适用于通用引用场景,在按值传递和指针参数并不适用。

如下是常用的场景:

template<typename T>
void fwd(T&& param)
{
f(std::forward<T>(param));
} template<typename... Ts>
void fwd(Ts&&... params)
{
f(std::forward<T>(param)...);
}

失败case一:Braced initializers(支撑初始化器)

void f(const std::vector<int>& v);

f({1,2,3});// ok, 隐式转换
fwd({1,2,3}); // compile error;
// 原因:推导传入fwd的参数类型,将其与f的声明类型比较 auto il = {1, 2, 3};
fwd(il); // ok
  • 当编译器不能推导出一个或者多个fwd参数类型,编译器会报错
  • 当编译器将一个或者多个fwd参数类型推导错误。

失败case二:0或者NULL作为空指针

0或者NULL作为空指针给模板时,类型推导只会推导出整型,而不是一个指针类型。建议使用nullptr代替

失败case三:仅声明的整数静态const数据成员

class Widget
{
public:
static const std::size_t MinVals = 28;
};
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals); //ok f(Widget::MinVals);//ok
fwd(Widget::MinVals);//可以编译,但link error const std::size_t Widget::MinVals; // 加上定义即可

六、智能指针

要点一:unique_ptr

// TODO

技术书籍 — EffectiveMordenCpp 研读的更多相关文章

  1. Kindle Unlimited上的技术书籍

            直达链接:Kindle Unlimited         前不久,亚马逊在中国也推出了电子书包月服务.消息不灵通的我过了好久才看到这个消息,随后第一时间上官网查看具体情况.      ...

  2. 转:研读代码必须掌握的Eclipse快捷键

    总结的很不错,而且有相应的用法,推荐!!! from: http://www.cnblogs.com/yanyansha/archive/2011/08/30/2159265.html 研读代码必须掌 ...

  3. iOS组件化思路-大神博客研读和思考

    一.大神博客研读 随着应用需求逐步迭代,应用的代码体积将会越来越大,为了更好的管理应用工程,我们开始借助CocoaPods版本管理工具对原有应用工程进行拆分.但是仅仅完成代码拆分还不足以解决业务之间的 ...

  4. Automatic Generation of Animated GIFs from Video论文研读及实现

    论文地址:Video2GIF: Automatic Generation of Animated GIFs from Video 视频的结构化分析是视频理解相关工作的关键.虽然本文是生成gif图,但是 ...

  5. AD预测论文研读系列2

    EARLY PREDICTION OF ALZHEIMER'S DISEASE DEMENTIA BASED ON BASELINE HIPPOCAMPAL MRI AND 1-YEAR FOLLOW ...

  6. AD预测论文研读系列1

    A Deep Learning Model to Predict a Diagnosis of Alzheimer Disease by Using 18F-FDG PET of the Brain ...

  7. QA系统Match-LSTM代码研读

    QA系统Match-LSTM代码研读 背景 在QA模型中,Match-LSTM是较早提出的,使用Prt-Net边界模型.本文是对阅读其实现代码的总结.主要思路是对照着论文和代码,对论文中模型的关键结构 ...

  8. GoogLeNetv4 论文研读笔记

    Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning 原文链接 摘要 向传统体系结构中引入 ...

  9. GoogLeNetv3 论文研读笔记

    Rethinking the Inception Architecture for Computer Vision 原文链接 摘要 卷积网络是目前最新的计算机视觉解决方案的核心,对于大多数任务而言,虽 ...

  10. GoogLeNetv2 论文研读笔记

    Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift 原文链接 摘要 ...

随机推荐

  1. archlinux开机出现错误Dependency failed for /home. Dependency failed for Local File System Time outwaiting for device /dev/disk/...

    错误如下 Dependency failed for /home. Dependency failed for Local File System Time outwaiting for device ...

  2. #线段树,排列组合#洛谷 6108 [Ynoi2009] rprsvq

    题目链接 分析 维护区间和以及区间平方和都比较简单,考虑答案是什么,根据方差公式的变形. \[ans=\frac{\sum_{j=1}^na_j^2}{n}-\frac{(\sum_{j=1}^na_ ...

  3. MyBatis ognl.NoSuchPropertyException

    描述 SpringBoot + Mybatis-plus 项目,运行时出现如下错误: ognl.NoSuchPropertyException:没有对应属性异常 Invalid bound state ...

  4. Spring Boot 版本 GA、RC、beta等含义

    GA General Availability,正式发布的版本,官方开始推荐广泛使用,国外有的用GA来表示release版本. RELEASE 正式发布版,官方推荐使用的版本,有的用GA来表示.比如s ...

  5. CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器

    CentOS-6.5快速搭建HTTP服务器和仅供授权用户登陆的FTP服务器 (2014-01-09 21:29:31) 转载▼ 标签: linux centos 服务器 http vsftp 分类:& ...

  6. redis 简单整理——bitmaps[十二]

    前言 简单介绍一下bitmaps这个东西. 正文 我们都知道bitmaps 翻译过来就是二进制. 那么二进制可以存一些什么呢? 图片.视频,还可也存些什么呢? 现代计算机用二进制(位)作为信息的基础单 ...

  7. jenkins 持续集成和交付——maven小栗子(四)

    前言 因为前面一节中介绍了自由风格模式,这里介绍一个专门为maven打造的模式,没错就是使用插件.这里写这个只是为了说明,如果我们构建一些不是那么复杂的项目,那么可以去使用一些插件,这样可以更加方便, ...

  8. three.js实现数字孪生3D仓库一期(开源)

    大家好,本文使用three.js实现了3D仓库一期项目,给出了代码,分析了关键点,感谢大家~ 关键词:数字孪生.three.js.Web3D.WebGL.智慧仓库.开源 代码:Github 我正在承接 ...

  9. 【Oracle】PL/SQL制作唯一标识,固定字段+日期+流水码,流水码每日重置

    [Oracle]PL/SQL制作唯一标识,固定字段+日期+流水码,流水码每日重置 首先创建序列 create sequence sequence_name minvalue 0 maxvalue 99 ...

  10. 剑指offer11(Java)-旋转数组中的最小值(简单)

    题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转.请返回旋转数 ...