[读书笔记]《Effective Modern C++》—— 类型推导、auto、decltype
2022/2/7 22:43:31
本文主要是介绍[读书笔记]《Effective Modern C++》—— 类型推导、auto、decltype,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 前言
- 条款一: 理解模板型别推导
- 数组实参
- 函数实参
- 条款二:理解 auto 的类型推导
- 使用 auto 的好处
- 显式类型初始化
- 条款三:理解 decltype
- 返回值类型后置
- 总结
前言
本文内容主要摘录自 《Effective Modern C++》,本文主要是将书中开头类型推导部分的内容放在一块进行说明,在再次品读这部分内容之前,对模板的认识就仅仅停留在模板是长这个样子的,使用的时候可以特化或者偏特化,对更深入的内容不曾有意识涉及。通过下面的内容对 C++ 的类型推导,以及 auto 和 decltype 等关键字也有了一定的了解,对于返回值后置的用法也不再感到摸不着头脑。也希望可以帮助到对相关类型推导及关键字同样有疑惑的同学,同样也更推荐直接去阅读原书的相关章节。
条款一: 理解模板型别推导
首先给出模板的一般定义,这里以函数模板为例:
template <typename T> void f(ParamType param);
这里上面编译期会通过输入参数进行两个类型推导,一个是 T 的类型,另一个就是 ParamType。其中 T 的类型不仅依赖于输入参数的类型,还要依赖于 ParamType 的形式。对于 ParamType 的形式,一共分下面三种不同的情况来讨论。
情况1:ParamType 具有指针或者引用
template <typename T> void f1(T& param); template <typename T> void f2(const T& param); template <typename T> void f3(T* param); template <typename T> void f4(const T* param); int x = 27; const int cx = x; const int &rx = x; const int* px = &x; f1(x); // T 类型:int, param 类型:int & f1(cx); // T 类型:const int, param 类型:const int & f1(rx); // T 类型:const int, param 类型:const int & f2(x); // T 类型:int, param 类型:const int & f2(cx); // T 类型:int, param 类型:const int & f2(rx); // T 类型:int, param 类型:const int & f3(&x); // T 类型:int, param 类型:int* f3(px); // T 类型:const int, param 类型: const int*
上面可以看到,如果 ParamType 的形式中就带有了 const,那么 T 的类型推导中就会忽略 const 属性。
情况2:ParamType 是万能引用
template <typename T> void f(T&& param); f(x); // T 类型:int&, param 类型:int& f(cx); // T 类型:const int&, param 类型:const int& f(rx); // T 类型:const int&, param 类型:const int& f(27); // T 类型:int, param 类型:int&&
万能引用会区别左值和右值,如果是左值,T 和 ParamType 都会被推导成左值引用,如果传入的是右值引用,则按照情况1的规则推导。
情况3:ParamType 非指针也非引用
template <typename T> void f(T param); // 按值传递 f(x); // T 类型:int, param 类型:int f(cx); // T 类型:int, param 类型:int f(rx); // T 类型:int, param 类型:int const int const* ptr = &x; f(ptr); // T 类型:const int*, param 类型:const int*
因为是按值传递,无论传入什么 param 都将是一个新的副本,这会使 T 和 ParamType 忽略引用性及 cv 特性。因为原对象不能修改不代表新的副本是不可修改的,所以这里会失去 cv 特性。
数组实参
这里注意模板当是数组实参时,直接值传递,数组会退化成指针。但是引用传递,就会向其传递一个实际的数组类型。
template <typename T> void f(T param); // 按值传递,数组退化成指针 template <typename T> void f(T& param); // 按引用传递,数组类型,携带 size 信息 template <typename T, std::size_t N> std::size_t arraySize(T(&)[N]) { // 这个编译期直接返回数组的元素个数 return N; }
函数实参
对应的,函数也会退化成指针。
void func(int, double) template <typename T> void f1(T param); // 按值传递,函数退化成指针 template <typename T> void f2(T& param); // 按引用传递,函数推导成引用 f1(func); // void(*)(int ,double) f2(func); // void(&)(int ,double)
条款二:理解 auto 的类型推导
以上 auto 的类型推导基本等同于模板推导,只是有一种情况是 auto 特殊的,就是初始化列表的情形 std::initializer_list<T>。
在 C++ 11 中为了支持统一的初始化,定义了初始化列表模板,并且只要是花括号的初始化,auto 推导类型就是 std::initializer_list,其也是一个模板,所以内部的数据类型要一致。 如果向对应的函数模板传入花括号,会直接编译报错。
在类型推导中 auto 就相当于扮演了模板中的 T 这个角色。并且在函数返回值和 lambda 表达式的形参中使用 auto,仅仅是表示使用模板类型推导而非 auto 型别推导(所以直接传入初始化列表形式是编译不过的)。(仅仅表示,并不指导具体推导规则)。
使用 auto 的好处
- 首先对于一些冗长的类型可以少些代码
// 不适用 auto std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> func = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2;}; // 使用 auto auto func = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2;};
- 避免出现显式类型不当导致的代码问题
// 示例1 vector<int> v; // 不使用 auto unsigned sz = v.size(); // 使用 auto auto sz = v.size(); // 示例2 map<string, int> m; // 不使用 auto for(const pair<string, int>& p: m) {...} // 使用 auto for(const auto& p: m) {...}
上面示例1显式定义 sz 变量为 unsigned 类型,在 32 位和 64 位机器上都是 32 位的,但是 v.size() 实际的返回类型为 size_t,其在 32 和 64 位机器上分别是 32 位和 64 位,这可能就会引起问题。
示例2 则是因为 map 的键是不可修改的,所以实际的类型为 pair<const string, int>
, 如果显式声明为 pair<string, int>
类型,编译器会把所有的对象都拷贝一遍,然后把 p 这个引用绑定到临时对象上,每次迭代结束,临时对象再析构一次,效率上大打折扣。
显式类型初始化
一个比较特别的例子是 vector 类型用 operator[] 访问,返回的不是一个 bool 类型,因为底层优化了 bool 的类型,使用 1 bit存储,返回的是 vector::reference, 其是一个代理类,可以进行隐士转换成 bool 类型。需要进行一个显式的类型定义,使 auto 推导成我们想要的类型。
vector<bool> b; auto data = b[1]; // auto 推导成 vector<bool>::reference 类型 auto data = static_cast<bool>(b[1]); // 强制推导成 bool 类型
包括一些人为要的类型隐式转换,带显式类型的初始化用法强制 auto 推导成我们想要的类型。
条款三:理解 decltype
decltype 可以给定一个变量或者是表达式,会返回对应表达式或者变量的确切类型。
bool f(const Widget& w); decltype(w); // const Widget& decltype(f); // bool(const Weight&) vector<int> v{1,2,3}; decltype(v); // vector<int> decltype(v[0]); // int&
返回值类型后置
// C++11 template<typename C, typename I> auto f1(C& c, I i) { return c[i]; } template<typename C, typename I> auto f2(C& c, I i) -> decltype(c[i]) { return c[i]; } // C++14 template<typename C, typename I> decltype(auto) f3(C& c, I i) { return c[i]; }
这里 auto 指定为返回的函数进行模板类型推导,并且其相当于 T,这里是按值传递,f1 函数如果不使用 decltype,会损失 cv 与引用特性,不用 decltype 类型推导,auto 返回的就是 int 类型,是一个右值,在使用时如果对其结果赋值就会编译不通过。
f2 则没有这个问题,decltype 会推导出其类型为 int&, 左值引用。并且这样返回值类型后置的好处是可以使用函数形参了。
上面的返回值是一个需要注意的点,还有一个注意的点是我们传入的模板类型,这里是左值引用,并且是非常量左值引用,就没办法传入一个右值,重载一个右值引用实参时一个办法,另一个更好的办法就是万能引用,为了能传递右值类型,需要配合 std::forward 完美转发一起使用。
template<typename C, typename I> auto f4(C&& c, I i) -> decltype(std::forward<C>(c)[i]) { return std::forward<C>(c)[i]; }
总结
上面通过介绍 C++ 的模板推导规则,介绍了在非引用和指针的情况下(值传递,所以会省略 cv 特性),普通指针或引用(保留 cv 及数组特性),万能引用(主要区分左右值) 3 种情况下的相关推导规则。
引出了 auto 基本与其上一致,唯一区别是为了统一初始化,增加了对初始化列表类型的推导和支持。
最后 decltype 就是如实地返回变量或者表达式的类型,更多地是用在返回值类型后置(需要使用形参)的情况。
这篇关于[读书笔记]《Effective Modern C++》—— 类型推导、auto、decltype的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-22怎么通过控制台去看我的页面渲染的内容在哪个文件中呢-icode9专业技术文章分享
- 2024-12-22el-tabs 组件只被引用了一次,但有时会渲染两次是什么原因?-icode9专业技术文章分享
- 2024-12-22wordpress有哪些好的安全插件?-icode9专业技术文章分享
- 2024-12-22wordpress如何查看系统有哪些cron任务?-icode9专业技术文章分享
- 2024-12-21Svg Sprite Icon教程:轻松入门与应用指南
- 2024-12-20Excel数据导出实战:新手必学的简单教程
- 2024-12-20RBAC的权限实战:新手入门教程
- 2024-12-20Svg Sprite Icon实战:从入门到上手的全面指南
- 2024-12-20LCD1602显示模块详解
- 2024-12-20利用Gemini构建处理各种PDF文档的Document AI管道