Home

cuipf blog

07 Aug 2017

C++11特性介绍——类型推导

类型推导

我们都知道, C++中, 类型有静态类型和动态类型的区别, C++在98中,提供了对了动态类型的支持, 那就是运行时的类型识别(RTTI); C++ 98中RTTI运算符只要是typeiddynamic_cast; 具体是用不再赘述;

RTTI的缺点

  1. RTTI不能否认的, 会在运行时带来一些开销; 所以一般编译器都会让用户选择关闭如: gcc中使用选项-fno-rttion控制;
  2. 一般来说, 运行时候才确定类型对于我们来说有点为时过晚, 我们更过需要在编译时候确定类型, 这时候RTTI显然无法满足;

这时候类型推导很好地解决了上面问题! 类型推导在模板和泛型编程中广泛应用!

C++ 11中类型推导实现方式主要有两种:

  • auto关键字;
  • decltype;

auto

auto优势

使用auto做类型推导有几个优势(或者说几种最常见用法):

  1. 简化代码, 增加代码可读性;

     void func(const std::vector<std::string>& names)
     {
         //相比较iter和iter1
         std::vector<std::string>::iterator iter1 = names.begin();
         for(auto iter = names.begin(); iter != names.end(); ++iter)
         {
    
         }
     }
    
    
  2. auto的“自适应性”能够在一定程度上支持泛型编程;

    
     //对于不同平台, strlen可能返回4字节整型, 也可能返回8字节整型 auto很好解决这个问题
     //等同于size_t
     auto len = strlen("hello");
     //在模板中, "自适应"体现的更加明显
     template<typename T1, typename T2>
     double Sum(T1& t1, T2& t2)
     {
         auto s = t1 + t2;
         return s;
     }
    
     auto ret = Sum<int, float>(a, b);    //s 推导为float
     auto ret1 = Sum<float, float>(c, b); //s 推导为float
    

    auto使用细则

  • auto 和指针和引用结合使用;

      int x = 1;
      int* y = &x;
      double foo();
      int& bar();
    
      auto * a = &x; //int*
      auto & b = x;  //int&
      auto c = y;    //int*
      auto * d = y;  //int*
      //auto * e = &foo(); //失败 指针不能指向一个临时变量
      //auto & f = foo();  //编译失败 nonconst的左值引用不能和一个临时变量绑定
      auto & g = bar();
    
  • auto 和const, volatile结合使用; C++ 11规定auto可以和cv限制符一起使用, 但是声明为auto的变量不能从初始化表达式中”带走”cv限制符;

      double foo();
      float* bar();
    
      const auto a = foo(); //const double
      const auto& b = foo(); //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 和初始化列表, new的配合使用;

      auto y{1}; //y:int
      auto z = new auto(1); //z:int
    
  • auto使用于for循环中, auto所有用法中, 这个是我目前使用频率最高的;

      int array[5] = { 1, 2, 3, 4, 5 };
      for (const auto& e : array)
      {
          cout << e << "\t";
      }
      cout << endl;
    
      vector<unsigned int> vec_for = { 11, 22, 33, 4 };
      for (auto i = vec_for.begin(); i != vec_for.end(); ++i)
      {
          cout << dec << *i << "\t";
      }
      cout << endl;
    
      for (const auto& e : vec_for)
      {
          cout << e << endl;	 //e是解引用后的对象
      }
      cout << endl;
    
  • auto 不能作为函数的形参; 编译不通过;
  • 结构体中, 非静态成员变量不能是auto;
  • 声明auto数组, 同样也不会编译通过;
  • 实例化模板时候, 使用auto作为模板参数, 编译不通过;

      std::vector<auto> v {1, 2}; //编译不通过
    

decltype

decltype的应用

  • 与typedef, using的结合使用;

      //我们在C++ 11头文件中经常见到这样的定义
      using size_t = decltype(sizeof(0));
      using ptrdiff_t = decltype((int*)0 - (int*)0);
      using nullptr_t = decltype(nullptr);
    
  • decltype用作变量的声明;

      std::vector<std::string> names;
      typedef decltype(names.begin()) NamesIterator;
      for(NamesIterator iter = names.begin(); iter != names.end(); ++iter)
      {
      }
      //等价于上面
      for(decltype(names)::iterator iter1 = names.begin(); iter != names.end(); ++iter)
      {
      }
    
  • C++中有时候我们也会经常遇到, 匿名类型; 拥有decltype后, 重用匿名类型也并非难事;

      enum class
      {
          DEBUG,
          INFO,
          TRACE
      } LogLevel;
    
      decltype(LogLevel) level1;
    
  • decltype 与模板以及泛型编程的结合;

      template<typename T1, typename T2>
      void Sum(const T1& t1, const T2& t2, decltype(t1 + t2)& s)
      {
          s = t1 + t2;
      };
      int a = 3;
      long b = 5;
      long e = 0;
      Sum(a, b, e); //s 被推导为long
    
    
  • 使用decltype追踪返回类型

      template<typename T1, typename T2>
      auto Mul(const T1& t1, const T2& t2) -> decltype(t1 * t2)
      {
          return t1 * t2;
      }
    
    

decltype使用原则

这里使用实例decltype(e)进行说明:

  • 如果e是没有带括号的标记符表达式或者类成员访问表达式, 那么decltype(e)就是e所命名的实体的类型;
  • 如果e是一个被重载的函数, 则会导致编译错误;
  • 假设e的类型是T, 则decltype(e)也是T;
  • 假设e的类型是T, 如果e是左值, 则decltype(e)是T&;
  • 假设e的类型是T, 如果e是将亡值, 则decltype(e)是T&&;
  • 与auto不同, auto类型推导时候不可以”带走”CV限制符, decltype推导则可以带走CV限制符;
//decltype使用 规则
int i = 4;
int arr[5] = { 0 };
int *ptr = arr;
struct S
{
	double d;
} s;
//右值引用
int && RvalRef();
void Overloaded(int);
void Overloaded(char);
/*
	规则1: 单个标记符表达式以及访问类成员 推导为本类型
*/
decltype(arr) var1;  //int[5] 标记符表达式
decltype(ptr) var2;  //int*
decltype(s.d) var3;  //double
decltype(Overloaded) var4; //编译失败 因为重载
/*
	规则2:将亡值 推导为类型的右值引用
*/
decltype(RvalRef()) var6 = 1; //int&&
/*
	规则3:左值,推导为类型的引用
*/
decltype(true ? i : i) var7 = i; //int& 三元运算符返回一个i的左值
decltype((i)) var8 = i;          //int& 带圆括号的左值
decltype(++i) var9 = i;          //int& ++i 返回i的左值
decltype(arr[3]) var10 = i;      //int& []返回左值
decltype(*ptr) var11 = i;        //int& *返回左值
decltype("lval") var12 = "lval"; //const char(&)[9]
/*
	规则4:以上都不是,推导为本类型
*/
decltype(1) var13;    //int
decltype(i++) var14;  //int i++返回右值

cuipf

scribble