一、类型推导

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. 误删除系统超级用户(superuser)权限的恢复方式

    在使用KingbaseES数据库的时候,系统默认存在一个跟系统初始化用户同名的Superuser(默认是system用户,可更改). 这个Superuser的存在其实对于权限的管控是很有用的,但是如果 ...

  2. Java开发岗面试题小结

    8种基本数据类型 类型名称 关键字 占用内存 取值范围 字节型 byte 1 字节 -128~127 短整型 short 2 字节 -32768~32767 整型 int 4 字节 -21474836 ...

  3. 【Java】请写出你最常见到的 5 个 runtime exception

    请写出你最常见到的 5 个 runtime exception. ​ 参考:https://blog.csdn.net/qq_20417499/article/details/80222820 Cla ...

  4. 深入剖析 Java 类属性与类方法的应用

    Java 类属性 Java 类属性,也称为字段,是类中的变量.它们用于存储与类相关的数据. 创建类属性 在类定义中声明属性: public class Main { int x; // 属性 Stri ...

  5. 每日一题--Python打印金字塔

    def day1(num): s = 'abcdefghijklmnopqrstuvwxyz' * (num // 26 + 1) for i in range(1, num + 1): print( ...

  6. openEuler20.03操作系统上安装部署MogDB2.1.1

    openEuler20.03 操作系统上安装部署 MogDB2.1.1 本文出处:https://www.modb.pro/db/378319 openEuler 操作系统上安装 mogdb: 下载 ...

  7. 及刻周边惠:拥抱HarmonyOS原子化服务

    原文链接:https://mp.weixin.qq.com/s/Y75eiRlvDLXzoZWzAiZdeg,点击链接查看更多技术内容: 开发背景 及刻周边惠是梦享网络旗下本地生活服务平台,旨在为消费 ...

  8. SVM三则

    硬间隔SVM SVM被提出来, 解决模式识别中, 数据的分类问题,属于有监督算法中的一种, 如上图所示, 于其他的线性回归方式不同, SVM企图去寻找一个最完美的超平面, 因为能正确分类样本的线, 它 ...

  9. vue中img标签图片 加载时 与 加载失败 的处理方法

    开发过程中经常需要和图片处理打交道,看了网上很多有关图片处理的方法,都是讲解得一知半解,没有比较全面的总结.下面,将简单总结一个我们通过vue去处理img标签过程中,图片加载时,与图片加载失败时的处理 ...

  10. 一些奇奇怪怪的js知识

    0.关于前端为什么typeof null 得到的结果是 object 对于 null 来说,很多人会认为他是个对象类型,其实这是错误的. 虽然 `typeof null` 会输出 `object`,但 ...