C++11:使用 auto/decltype/result_of使代码可读易维护

来源:互联网 发布:php 获取当前地区 编辑:程序博客网 时间:2024/05/17 17:42

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 styleconst 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 errorauto g = bar(); // intauto &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 doubleconst auto &b = x; // const double &volatile auto *c = bar(); // volatile float *auto d = a; // doubleauto &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 scenariotemplate<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)进行判断。这里就要说说如何获取闭包的返回值类型了。获取闭包的返回值类型的方法有三种:

  1.     通过decltype
  2.     通过declval
  3.     通过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 ageauto r1 = range.groupby([](const Person& person){return person.age; });// group by nameauto r2 = range.groupby([](const Person& person){return person.name; });// group by cityauto 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

4 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 微信红包充话费不到账怎么办 支付宝充话费等待第三方发货怎么办 微信充话费显示成功但没收到怎么办 微信退款一直在退款中怎么办 文件大于100发不了微信怎么办 微信的传送文件大于100怎么办 微信钱包话费充值错误怎么办 微信转账到不了账也退不回是怎么办 求人办事微信发红包对方不收怎么办 微信上交了订金对方不退怎么办 交通事故对方伤员堵大门搂腿怎么办 电脑开机桌面文件都没了怎么办 qq飞车手游队长换了微信群怎么办 qq飞车手游登录授权失败怎么办 安装时提示安装包发现错误怎么办 苹果6p升级系统验证失败怎么办 w10开不了机无限重启怎么办 微信朋友圈里的表情图打不开怎么办 金立手机微信启动录音被拒绝怎么办 微信帐号解封后漂流瓶不能用怎么办 微信怎么在电脑上登不上去怎么办 玩旧版60级魔兽经常花屏怎么办? 我的世界手机版物品栏不见了怎么办 苹果手机掉进水里出现花屏该怎么办 球球大作战还没进去停止运行怎么办 ps3 e3硬破芯片坏了怎么办 电话打开后页面上没有东西怎么办 WPS在电脑安装后卸载不了怎么办 ps总要以管理员的身份打开怎么办 3d关的慢保存慢怎么办 无法与服务器建立可靠的连接怎么办 被抵押的房子开发商不解押怎么办 手机系统语言是英文没有中文怎么办 w7主机网插口灯不亮了没网怎么办 电脑用了5年变得很卡了怎么办 苹果6s系统占12g怎么办 百度网盘下载的压缩包打不开怎么办 三星手机微信安装包解析错误怎么办 下身流出来的东西有异味很重怎么办 鞋脚底总有一股酸臭的味道怎么办 腋窝总是有股酸臭的味道应该怎么办