一、类型推导

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. KingabseES例程-函数和过程的 INVOKER 与 DEFINER

    调用者权利和定义者权利子句 指定子程序的权利属性.权利属性影响单元在运行时,执行的SQL语句的名称解析和权限检查. PG模式: SECURITY INVOKER SECURITY DEFINER Or ...

  2. Python实现结巴分词统计高频中文词汇

    代码 1 # 读取文件 2 fn = open('youxi.txt', 'rt', encoding='utf-8') # 打开文件 3 string_data = fn.read() # 读出整个 ...

  3. Linux服务器程序规范化

    Linux日志体系 rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志.用户进程是通过调用syslog函数生成系统日志的.该函数将日志输出到一个UNIX本地域socket类型(AF_ ...

  4. 【AI】『Suno』哎呦不错呦,AI界的周董,快来创作你的歌曲吧!

    前言 缘由 Suno AI的旋风终于还是吹到了音乐圈 事情起因: 朋友说他练习时长两天半,用Suno发布了首张AI音乐专辑.震惊之余,第一反应是音乐圈门槛也这么低了,什么妖魔鬼怪都可以进军了嘛! 好奇 ...

  5. 做了5年开源项目,我总结了以下提PR经验!

    如何优雅地参与开源贡献,向顶级开源项目提交 PR(Pull Request),如何更好地提交 PR? 针对这些问题和疑惑,我们邀请了 OpenAtom OpenHarmony(以下简称"Op ...

  6. Git安装和配置教程:Windows/Mac/Linux三平台详细图文教程,带你一次性搞定Git环境

    Git是一款免费.开源的分布式版本控制系统,广泛应用于软件开发领域.随着开源和云计算的发展,Git已经成为了开发者必备的工具之一.本文将为大家介绍Git在Windows.Mac和Linux三个平台上的 ...

  7. 【Insights直播】3D建模服务,快速构建高质量3D模型

    2021年7月15日,HMS Core 6.0面向全球开发者正式上线.华为在HMS Core 6.0中,为开发者开放了一个全新的服务--3D建模服务(3D Modeling Kit),为应用开发者提供 ...

  8. Spring的事务管理方式编程式和声名式

    spring的事务管理方式: 一.声名式 二.编程式 事务:查询不会影响数据的改变,对于增删改必须进行事务的管理.如果没有事务管理spring也提供了默认传播方式REQUIRED 一.声名式事务管理( ...

  9. sql 语句系列(月份的第一天和最后一天)[八百章之第二十章]

    前言 插播一个,从给定日期值里面提取年月日时分秒. 之所以写这个是因为使用频率太高. mysql: select DATE_FORMAT(CURRENT_TIMESTAMP,'%k') hr, DAT ...

  10. python flashtext字符串快速替换,自然语言处理加速

    在自然语言处理当中,经常对数据集进行一些数据字符的替换,表情的替换,以便在tokenizer的时候不被识别成[unk],造成信息的缺失 常规方法使用python自带的replace方法实现,但数据量很 ...