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

上一篇:JavaScript语言精粹 笔记04 数组


下一篇:使用IIS Server Farms搭建应用服务负载均衡