C++11:使用 auto/decltype/result_of使代码可读易维护
C++11 终于加入了自动类型推导。以前,我们不得不使用Boost的相关组件来实现,现在,我们可以使用“原生态”的自动类型推导了!
C++引入自动的类型推导,并不是在向动态语言(强类型语言又称静态类型语言,是指需要进行变量/对象类型声明的语言,一般情况下需要编译执行。例如C/C++/Java;弱类型语言又称动态类型语言,是指不需要进行变量/对象类型声明的语言,一般情况下不需要编译(但也有编译型的)。例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等)靠拢,通过弱化类型实现编程的灵活性;而是在保持类型安全的前提下提高代码的可读性,易用性和通用性。要理解这点就必须对C++泛型编程(GP)和为什么要泛型有一定认识,推荐阅读:刘未鹏:泛型编程:源起、实现与意义。类型自动推导带来的最大好处就是拥有初始化表达式的负责类型声明简化了。很多时候,名字空间,模版成了类型的一部分,经常要写很长的表达式,不小心写错了,编译器就给爆出一堆近似与乱码的错误信息,调试起来更是头疼。
1) auto
简单用法:
map< int, map<int,int> > m;
// C++98/03 style:
map<int, map<int,int> >::const_iterator it = m.begin();
// C++11 style
const auto it = m.begin();
其实,我们只需要知道it是迭代器就行,通过它可以访问容器的元素。而且如果要修改m的类型,那么导致大量的迭代器都要修改,违反了DRY(Don't Repeat Yourself,不要重复粘帖你的代码)原则。即使使用typedef,也不能完全避免这个问题。所以,任何人都应该使用auto!(注:auto的语义已经更改,C++98/03是修饰自动存储期的局部变量。但是实际上这个关键字几乎没有人用,因为一般函数内没有声明为static的变量都是自动存储期的局部变量)
本文讲详细讨论auto/decltype/result_of 用法及应用场景。
auto并不是说这个变量的类型不定,或者在运行时再确定,而是说这个变量在编译时可以由编译器推导出来,使用auto和decltype只是占位符的作用,告诉编译器这个变量的类型需要编译器推导,借以消除C++变量定义和使用时的类型冗余,从而解放了程序员打更多无意义的字符,避免了手动推导某个变量的类型,甚至有时候需要很多额外的开销才能绕过的类型声明。
但是,auto不能解决精度问题:
auto a = numeric_limits<unsinged int>::max();
auto b = 1;
auto c = a + b;// c is also unsigned int, and it is 0 since it has overflowed.
这并不像一些动态语言那样,会自动扩展c以存储更大的值。因此这点要注意。
auto的使用细则:
int x;
int *ptr = &y;
double foo();
int &bar(); auto *a = &x; // int *
auto &b = x; // int &
auto c = ptr; //int *
auto &d = ptr; // int *
auto *e = &foo(); // compiler error, the pointer cannot point to a temporary variable.
auto &f = foo(); // compiler error
auto g = bar(); // int
auto &h = bar(); // int &
变量a, c , d都是指针,且都指向x,实际上对于a,c,d三个变量而言,声明其为auto *或者auto并没有区别。但是,如果变量要是另外一个变量的引用,则必须使用auto &,注意g和h的区别。
auto和const,volatile和存在这一定的关系。C++将volatile和const成为cv-qualifier。鉴于cv限制符的特殊性,C++11标准规定auto可以和cv-qualifier一切使用,但是auto声明的变量并不能从其初始化表达式中带走cv-qualifier。还是通过实例理解吧:
double x;
float * bar(); const auto a = foo(); // const double
const auto &b = x; // const double &
volatile auto *c = bar(); // volatile float * auto d = a; // double
auto &e = a; // const double &
auto f = c; // float *
volatile auto &g = c; // volatile float * &
注意auto声明的变量d,f都无法带走a 和f的const和volatile。但是例外是引用和指针,声明的变量e和g都保持了其引用对象相同的属性。
2) decltype
decltype主要为库作者所用,但是如果是我们需要用template,那么使用它也能简洁我们的代码。
与C完全不支持动态类型的是,C++在C++98标准中就部分支持动态类型了:RTTI(RunTime Type Identification)。RTTI就是为每个类型产生一个type_info的数据,我们可以使用typeid(var)来获取一个变量的type_info。type_info::name就是类型的名字;type_info::hash_code()是C++11中新增的,返回该类型唯一的hash值。
在decltype产生之前,很多编译器厂商都有自己的类型推导的扩展,比如GCC的typeof操作符。
言归正传,decltype就是不用计算表达式就可以推导出表达式所得值的类型。
template<typename T1, typename T2>
void sum(T1 &t1, T2 &t2, decltype(t1 + t2) &s){
s = t1 + t2;
} // another scenario template<typename T1, typename T2>
auto sum(T1 &t1, T2 &t2) ->decltype(t1 + t2)){
return t1 + t2;
}
很容易看出与auto的不同。实例化模版的时候,decltype也可以有用武之地:
int hash(char *); map<char *, decltype(hash(nullptr))> m;// map<char *, int> m may be more simple, but when hash value changed to other type, such as
//string, it would cause a lot of maintenance effort.
接下来看一个更复杂的例子。首先定义Person:
struct Person
{
string name;
int age;
string city;
};
我们想得到一系列的multimap,可以按照city,age进行分组。
第一个版本:
template<typename T, typename Fn>
multimap<T, Person> GroupBy(const vector<Person>& vt, const Fn& keySlector)
{
multimap<T, Person> map;
std::for_each(vt.begin(), vt.end(), [&map](const Person& person)
{
map.insert(make_pair(keySlector(person), person)); //keySlector返回值就是键值,通过keySelector擦除了类型
}); return map;
}
通过传入key type,和获取相应值的函数(可以使用lambda),就可以获取这个multimap。但是,实际上key type就是Fn的返回值,可以不用传入:通过keySlector(person)进行判断。这里就要说说如何获取闭包的返回值类型了。获取闭包的返回值类型的方法有三种:
- 通过decltype
- 通过declval
- 通过result_of
第一种方式,通过decltype:
multimap<decltype(keySlector((Person&)nulltype)), Person>或者multimap<decltype(keySlector(*((Person*)0))), Person>
这种方式可以解决问题,但不够好,因为它有两个magic number:nulltype和0。
通过declval:
multimap<decltype(declval(Fn)(declval(Person))), Person>
这种方式用到了declval,declval的强大之处在于它能获取任何类型的右值引用,而不管它是不是有默认构造函数,因此我们通过declval(Fn)获得了function的右值引用,然后再调用形参declval(Person)的右值引用,需要注意的是declval获取的右值引用不能用于求值,因此我们需要用decltype来推断出最终的返回值。这种方式比刚才那种方式要好一点,因为消除了魔法数,但是感觉稍微有点麻烦,写的代码有点繁琐,有更好的方式吗?看第三种方式吧:
通过result_of
multimap<typename std::result_of<Fn(Person)>::type, Person>
std::result_of<Fn(Arg)>::type可以获取function的返回值,没有魔法数,也没有declval繁琐的写法,很优雅。其实,查看源码就知道result_of内部就是通过declval实现的,作法和方式二一样,只是简化了写法。
最终版本:
vector<Person> v = { {"aa", 20, "shanghai"}, { "bb", 25, "beijing" }, { "cc", 25, "nanjing" }, { "dd", 20, "nanjing" } };
typedef typename vector<Persion>::value_type value_type;
template<typename Fn>
multimap<typename result_of<Fn(value_type)>::type, value_type> groupby(const vector<value_type> &v, const Fn& f) // -> decltype(f(*((value_type*)0))),f((value_type&)nullptr)
{
//typedef typename result_of<Fn(value_type)>::type ketype;
typedef decltype(declval<Fn>()(declval <value_type>())) ketype; multimap<ketype, value_type> mymap;
std::for_each(begin(v), end(v), [&mymap, &f](value_type item)
{
mymap.insert(make_pair(f(item), item));
});
return mymap;
}
看一下最终的调用情况:
vector<Person> v = { {"aa", 20, "shanghai"}, { "bb", 25, "beijing" }, { "cc", 25, "nanjing" }, { "dd", 20, "nanjing" } };
// group by age
auto r1 = range.groupby([](const Person& person){return person.age; });
// group by name
auto r2 = range.groupby([](const Person& person){return person.name; });
// group by city
auto r3 = range.groupby([](const Person& person){return person.city; });
result_of 其实就是通过decltype来推导函数的返回类型。result_of的一种可能的实现如下:
template<class F, class... ArgTypes>
struct result_of<F(ArgTypes...)>
{
typedef decltype(
declval<F>()(declval<ArgTypes>()...)
) type;
}
另外一个使用result_of的例子:
template< class Obj >
class CalculusVer2 {
public:
template<class Arg>
typename std::result_of<Obj(Arg)>::type operator()(Arg& a) const
{
return member(a);
}
private:
Obj member;
};
总结:
auto适用于任何人,除非需要类型转换,否则你应该使用它
decltype适合推导表达式,因此在库中大量使用,当然它也可以推导函数的返回值,但是函数的返回值的推导,还是交给result_of吧!
引用:
http://www.cnblogs.com/qicosmos/p/3286057.html
C++11:使用 auto/decltype/result_of使代码可读易维护的更多相关文章
- 【C++】C++11的auto和decltype关键字
转自: http://www.linuxidc.com/Linux/2015-02/113568.htm 今天要介绍C++11中两个重要的关键字,即auto和decltype.实际上在C++98中,已 ...
- c++ 11 游记 之 decltype constexpr
title: c++ 11 游记 1 keyword :c++ 11 decltype constexpr 作者:titer1 zhangyu 出处:www.drysaltery.com 联系:130 ...
- guava 学习笔记 使用瓜娃(guava)的选择和预判断使代码变得简洁
guava 学习笔记 使用瓜娃(guava)的选择和预判断使代码变得简洁 1,本文翻译自 http://eclipsesource.com/blogs/2012/06/06/cleaner-code- ...
- Android 命名规范 (提高代码可以读性)
android文件众多,根据名称来辨别用途很重要,因此命名要规范 这篇文章可参考:Android 命名规范 (提高代码可以读性) 刚接触android的时候,命名都是按照拼音来,所以有的时候想看懂命名 ...
- 程序员需要掌握的七种 Python 代码更易维护的武器
检查你的代码风格 PEP 8 是 Python 代码风格规范,它规定了类似行长度.缩进.多行表达式.变量命名约定等内容.尽管你的团队自身可能也会有稍微不同于 PEP 8 的代码风格规范,但任何代码风格 ...
- 让 Python 代码更易维护的七种武器——代码风格(pylint、Flake8、Isort、Autopep8、Yapf、Black)测试覆盖率(Coverage)CI(JK)
让 Python 代码更易维护的七种武器 2018/09/29 · 基础知识 · 武器 原文出处: Jeff Triplett 译文出处:linux中国-Hank Chow 检查你的代码的质 ...
- android中的http框架,使其更加简单易用
Afinal 是一个android的sqlite orm 和 ioc 框架. Afinal 是一个android的sqlite orm 和 ioc 框架.同时封装了android中的http框架,使其 ...
- 可爱的豆子——使用Beans思想让Python代码更易维护
title: 可爱的豆子--使用Beans思想让Python代码更易维护 toc: false comments: true date: 2016-06-19 21:43:33 tags: [Pyth ...
- Apache Spark源码走读之17 -- 如何进行代码跟读
欢迎转载,转载请注明出处,徽沪一郎 概要 今天不谈Spark中什么复杂的技术实现,只稍为聊聊如何进行代码跟读.众所周知,Spark使用scala进行开发,由于scala有众多的语法糖,很多时候代码跟着 ...
随机推荐
- JavaScript 调试
在编写 JavaScript 时,如果没有调试工具将是一件很痛苦的事情. JavaScript 调试 没有调试工具是很难去编写 JavaScript 程序的. 你的代码可能包含语法错误,逻辑错误,如果 ...
- Android RRO机制的运用-----google开机向导客制化
上周五的时候领导分了一个任务,客户让在google开机向导里面增加一页,首先就想到了android的Overlay,然后网上搜了下,发下有很多人写了这方面的技术.而且写的都还不错,所以本篇只当记录作用 ...
- android MultiDex multiDex原理(一)
android MultiDex 原理(一) Android分包MultiDex原理详解 转载请注明:http://blog.csdn.net/djy1992/article/details/5116 ...
- SRAM/DRAM,PROM/EPROM/EEPROM,NOR/NAND FLASH区别
SRAM/DRAM,PROM/EPROM/EEPROM,NOR/NAND FLASH区别 RAM / ROM 存储器 ROM和RAM指的都是半导体存储器,R ...
- Hive-ORC文件存储格式(续)
本文在Hive-ORC文件存储格式的理论基础上,进一步分析一个实际的Hive ORC表中的数据存储形式. 一.表结构 库名+表名:fileformat.test_orc 字段 类型 category_ ...
- RxJava(一) create操作符的用法和源码分析
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/51524470 本文出自:[余志强的博客] 1 create操作符的基 ...
- SpriteKit:检测当新场景显示以后
Detecting When a New Scene Is Presented Sprite Kit在SKScene类中提供2个可以重载的方法用来检测当一个场景过渡出去或过渡进来的时候. 第一个方法是 ...
- EJB_开发消息驱动bean
开发消息驱动bean Java消息服务(Java MessageService) Java 消息服务(Java Message Service,简称 JMS)是用于访问企业消息系统的开发商中立的API ...
- Makefile常用函数总结
在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具 有智能.make所支持的函数也不算很多,不过已经足够我们的操作了.函数调用后,函 数的返回值可以当做变量来使用. 一 ...
- x264源代码简单分析:滤波(Filter)部分
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...