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)进行判断。这里就要说说如何获取闭包的返回值类型了。获取闭包的返回值类型的方法有三种:

  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 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

时间: 2024-11-08 18:55:14

C++11:使用 auto/decltype/result_of使代码可读易维护的相关文章

《Web前端开发最佳实践》——1.3 规范的Web前端代码:更易维护、更高性能和更安全

1.3 规范的Web前端代码:更易维护.更高性能和更安全 规范的代码,这是所有软件开发中对代码的基本要求,前端开发也是一样的,要求编写规范的HTML.CSS和JavaScript代码. 什么样的前端代码才能称得上规范的代码?探讨这个问题之前,首先需要强调的是规范不是标准,不是放之四海而皆准的,不同的项目中的代码规范是有可能有差异的,比如命名,有些项目规定HTML标签的id必须要以控件的缩写名作为前缀,如按钮的id名以"btn"作为前缀,有些只是规定命名有意义就可以.再比如有些项目规定J

重构-使代码更简洁优美:实际经验之谈(提供一技巧,让你省掉N多代码)

这几天没怎么写文,因为在用 CYQ.Data  框架 重构以前的一个博客源码,而在重构的过程中,最关键的就是简化代码了.   今天,我将说一个很典型的示例,看完本示例后,不要惊讶,不要怀疑,它不是神马,也不是浮云,   而是很实在的一种方式,能让你节省了N多的代码,让你的代码看起来更简洁优美.   而这里说的一个很典型的示例,是从我目前重构中的博客中应用而来的:   一:正常的开发方式   1:扫一眼当前的项目解决方案   2:说说Module库 一般我们很常见的会有一个页面基类,当然各花叫法不

重构-使代码更简洁优美II:实际经验之谈(项目分层是怎么扯上代码节省的)

前言: 好几天没写文了,因为在折腾传说中的8国语言博客,实际目前预定义了10国+1自定义语言,代码还在慢慢的写着写着~~~~ 目前最新进展预览网址为:http://cyq.tupianshop.com/ ,其强大之处及 CYQ.Data 框架 V3.N 系列   后文再介绍了. 写文章有时候是需要有灵感或一时的冲动的~比如刚刚在改博客代码,经过一段思考,得到一些灵感,便有了此文.   在很久很久的 Long Long Ago 以前,写过一篇文章:重构-使代码更简洁优美:实际经验之谈(提供一技巧,

《众妙之门——JavaScript与jQuery技术精粹》——2.2 在哪里可以使代码得到复查?

2.2 在哪里可以使代码得到复查? 一般最具挑战性的部分在于找到一个值得信任的有经验的开发者来帮我们复查.以下是一些可以请求别人复查代码的地方(有时是别国语言). . JSMentorsJSMentors是一个讨论JavaScript相关内容的邮件列表,其复查面板中有一大批有经验的开发者(包括JD Dalton.Angus Croll和Nicholas Zakas)在复查人员名单上.这些老师不一定一直在线,但是对于提交的代码他们都会尽全力提供有用的.建设性的反馈意见.如果希望获得的是基于某种特殊

深入分析:C++模板究竟会使代码膨胀吗_C 语言

今天和同事说到C++模板会使代码膨胀, 可同事觉得不会.同事的依据是: 如果模板会使代码膨胀, 那么ATL和WTL里为什么还要大量使用模板? 同样功能 ,ATL和WTL编译出的可执行文件可比MFC编译的要小的多.我当时一愣 ,事实确实如同事所说,难道模板会使代码膨胀的观点是错误的吗? MFC因为本身代码量和复杂性在那里, 所以它生成比较大的exe无可厚非.我们这里重点关注为什么ATL/WTL使用模板,但是却不会使生成的exe变大. 我们知道使用模板时, 同一模板生成不同的模板实类后会是多份代码

基础才是重中之重~Conditional特性使代码根据条件在debug或者release模式中执行

众所周知,自从.net中出现了特性(attribute)之后,代码变得更加简洁,可读性更强了,今天主要说一下Conditional特性,Conditional它主要是约束你的代码段在哪种条件下进行执行,我们今天主要针对DEBUG和TRACE这两个条件约束,它们可以当作是Conditional特性的输入参数,形式如下: 1 [Conditional("DEBUG")] 2 protected static void LogDebugInfo() 3 { 4 //在debug模式输出一些调

《有效的单元测试》一2.1 可读的代码才是可维护的代码

2.1 可读的代码才是可维护的代码 昨天我从咨询工作现场回到办公室,与同事谈起他近期要参加的1K大赛.这种比赛是demo party的传统节目--demo party是一种极客聚会,黑客们会带着计算机.睡袋.能量饮料在巨大的舞台上待上整个周末.从第一届开始,黑客们就互相较劲,在很多人认为过时的硬件上舞弄着疯狂的技巧来制作3D动画.这种动画的一个典型约束是大小.在我同事要准备的比赛中,其名字1K意味着代码编译为二进制之后的大小不能超过1024字节.对,你没听错--1024字节.为了把有用的程序装入

赫夫曼编码有部分代码没读明白 有n个 权值 为什么编码结束符在n-1的位置 ?cd[n-1]?

问题描述 赫夫曼编码有部分代码没读明白 有n个 权值 为什么编码结束符在n-1的位置 ?cd[n-1]? #include #include #include typedef struct{ unsigned int weight; int parent,lchild,rchild; }HTNode,*HuffmanTree; //动态分配数组存储赫夫曼树 typedef char **HuffmanCode;//动态分配数组存储赫夫曼编码表 void Select(HuffmanTree HT

Apache Spark源码走读(九)如何进行代码跟读&amp;使用Intellij idea调试Spark源码

<一>如何进行代码跟读 概要 今天不谈Spark中什么复杂的技术实现,只稍为聊聊如何进行代码跟读.众所周知,Spark使用scala进行开发,由于scala有众多的语法糖,很多时候代码跟着跟着就觉着线索跟丢掉了,另外Spark基于Akka来进行消息交互,那如何知道谁是接收方呢? new Throwable().printStackTrace 代码跟读的时候,经常会借助于日志,针对日志中输出的每一句,我们都很想知道它们的调用者是谁.但有时苦于对spark系统的了解程度不深,或者对scala认识不