技术书籍 — EffectiveMordenCpp 研读
一、类型推导
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::move或std::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. 此处返回的已经不是局部对象 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_if和std::decay、std::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 研读的更多相关文章
- Kindle Unlimited上的技术书籍
直达链接:Kindle Unlimited 前不久,亚马逊在中国也推出了电子书包月服务.消息不灵通的我过了好久才看到这个消息,随后第一时间上官网查看具体情况. ...
- 转:研读代码必须掌握的Eclipse快捷键
总结的很不错,而且有相应的用法,推荐!!! from: http://www.cnblogs.com/yanyansha/archive/2011/08/30/2159265.html 研读代码必须掌 ...
- iOS组件化思路-大神博客研读和思考
一.大神博客研读 随着应用需求逐步迭代,应用的代码体积将会越来越大,为了更好的管理应用工程,我们开始借助CocoaPods版本管理工具对原有应用工程进行拆分.但是仅仅完成代码拆分还不足以解决业务之间的 ...
- Automatic Generation of Animated GIFs from Video论文研读及实现
论文地址:Video2GIF: Automatic Generation of Animated GIFs from Video 视频的结构化分析是视频理解相关工作的关键.虽然本文是生成gif图,但是 ...
- AD预测论文研读系列2
EARLY PREDICTION OF ALZHEIMER'S DISEASE DEMENTIA BASED ON BASELINE HIPPOCAMPAL MRI AND 1-YEAR FOLLOW ...
- AD预测论文研读系列1
A Deep Learning Model to Predict a Diagnosis of Alzheimer Disease by Using 18F-FDG PET of the Brain ...
- QA系统Match-LSTM代码研读
QA系统Match-LSTM代码研读 背景 在QA模型中,Match-LSTM是较早提出的,使用Prt-Net边界模型.本文是对阅读其实现代码的总结.主要思路是对照着论文和代码,对论文中模型的关键结构 ...
- GoogLeNetv4 论文研读笔记
Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning 原文链接 摘要 向传统体系结构中引入 ...
- GoogLeNetv3 论文研读笔记
Rethinking the Inception Architecture for Computer Vision 原文链接 摘要 卷积网络是目前最新的计算机视觉解决方案的核心,对于大多数任务而言,虽 ...
- GoogLeNetv2 论文研读笔记
Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift 原文链接 摘要 ...
随机推荐
- 10 JavaScrit定时器
10 JavaScrit定时器 在JS中, 有两种设置定时器的方案: // 语法规则 t = setTimeout(函数, 时间) // 经过xxx时间后, 执行xxx函数 // 5秒后打印我爱你 t ...
- 成为一名 BI数据分析师,这些能力不能少
近些年来,随着数据技能的日益普及和数据工具的不断简化,大数据技术的迅速发展催生了很多新生职业,BI数据分析师就是其中一个岗位. 说到BI数据分析,我们首先要说的是 BI,它的全称是 Business ...
- Djangorestframework 记录一个报错 -- rest_framework.authentication.ToKenAuthentication
今天在使用 Djangorestframework 这个框架时,发生报错: ImportError: Could not import 'rest_framework.authentication.T ...
- 开源在线表单工具 HeyForm 使用教程
HeyForm 是一个非常出色的开源在线表单工具,可以通过直观的拖拽式编辑器,快速构建出美观实用的表单. HeyForm 的功能非常丰富: 支持丰富的输入类型,从基础的文本.数字到高级的图片选择.日期 ...
- Kafka 的分片和副本机制
我们在使用 Kafka 生产和消费消息的时候,肯定是希望能够将数据均匀地分配到所有服务器上.比如在日志收集场景,数据量是非常巨大的,例如大批量的集群每分钟产生的日志都能以 GB 计,所以如何将这么大的 ...
- 力扣907(java)-子数组的最小值之和(中等)
题目: 给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组. 由于答案可能很大,因此 返回答案模 10^9 + 7 . 示例 1: 输入:arr = ...
- 重新定义容器化 Serverless 应用的数据访问
简介: 本文首先聚焦到 AI 和大数据等应用 Serverless 化的最大挑战:计算和存储分离架构带来的数据访问延迟和远程拉取数据带宽巨大的挑战.尤其在 GPU 深度学习训练场景中,迭代式的远程读取 ...
- 一个 Blink 小白的成长之路
写在前面 写过blink sql的同学应该都有体会,明明写的时候就很顺滑,小手一抖,洋洋洒洒三百行代码,一气呵成.结果跑的时候,吞吐量就是上不去.导致数据延迟高,消息严重积压,被业务方疯狂吐槽.这时候 ...
- 科普达人丨漫画图解什么是eRDMA?
简介: 绕过CPU,将数据直接从一台计算机的内存传输到另一台计算机,进行网络加速 在一个领先的阿里云数据中心里,数百台服务器(也就是大型的计算机)在疯狂工作和通信,他们正在合力完成一个大型的大数据处理 ...
- 平台建设的7大问题:蚂蚁AI平台实践深度总结
简介: 在支持蚂蚁几乎所有核心业务运行和发展的过程中,我们在平台建设.业务支持.平台运营.AI创新以及AI整体运营等各个方面做了很多尝试,有了不少的收获和感悟,在此分享给大家. 过去几年,我和团队一直 ...