前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++11】消除重复, 提升代码质量---type_tratis

【C++11】消除重复, 提升代码质量---type_tratis

作者头像
CPP开发前沿
发布2021-11-16 14:05:46
1.6K0
发布2021-11-16 14:05:46
举报
文章被收录于专栏:CPP开发前沿CPP开发前沿

在《代码大全》书中对代码的圈复杂度规则进行了说明,具体如下:

  • 从函数第一行开始,一直往下看程序;
  • 遇到以下关键字或者同类的字那么圈复杂度加1;关键字如下:if,while,for,end,or,repeat等;
  • switch中case语句的每种情况都新增一个圈复杂度。

为了解决因为代码圈复杂度产生的代码质量问题,C++11提供了type_tratis类型萃取功能,在一定程度上可以消除冗长的代码分支语句,降低圈复杂度进而提升代码的可维护性。

1 基本的type_traits

C++ 11之前通过const或者enum枚举定义一个编译期常量的类型,在C++11中,则不需要这么定义,只需要从std::integral_constant进行派生即可。

1.1 定义编译期常量

C++11中可以从std::integral_constant派生,定义自己的编译期常量,std::integral_constant的定义原型为:

代码语言:javascript
复制
template <class T, T v>
struct integral_constant {
  static constexpr T value = v;
  typedef T value_type;
  typedef integral_constant<T,v> type;
  constexpr operator T() { return v; }
};

在integral_constant类中,可以通过成员变量value获取定义的编译期常量值。使用方法也很简单,派生integral_constant类后,则不用再新增定义类型和枚举变量。如下所示:

代码语言:javascript
复制
template <unsigned n>
struct factorial : std::integral_constant<int,n * factorial<n-1>::value> {};

template <>
struct factorial<0> : std::integral_constant<int,1> {};

int main() {
  std::cout << factorial<5>::value;  // constexpr (no calculations on runtime)
  return 0;
}

上面的代码通过继承integral_constant实现一个阶乘,程序输出结果为:120

1.2 类型判断

type_traits从integral_constant派生而来,使用这些方法可以在编译期判断数据类型,常用的类型判断类型有:is_const,is_void,is_union,is_class等等,具体可参下面的链接,

http://www.cplusplus.com/reference/type_traits/is_const/

使用方法如下:

代码语言:javascript
复制
int main() {
  std::cout << std::boolalpha;
  std::cout << "is_const:" << std::endl;
  std::cout << "int: " << std::is_const<int>::value << std::endl;
  std::cout << "const int: " << std::is_const<const int>::value << std::endl;
  std::cout << "const int*: " << std::is_const<const int*>::value << std::endl;
  std::cout << "int* const: " << std::is_const<int* const>::value << std::endl;
  return 0;
}

运行结果如下:

代码语言:javascript
复制
is_const:
int: false
const int: true
const int*: false
int* const: true

如要说明的是,有些人可能对std::is_const<const int*>::value这个返回值false有疑问,其实const int *是说指针是一个常量,但是指向的内存地址中的值是可以变化的,并不是常量。所以这里返回的事false。

1.2 判断两个类型之间的关系

traits同样提供了方法判断两个类型之间的关系,如:判断两个类型之间的相等或者继承关系traits主要提供了三种关系判断方法,主要是:is_same,is_base_of,is_convertible.

(1) is_same用法

is_same是用来在编译器判断两种类型是否相同。用法如下:

代码语言:javascript
复制
typedef int integer_type;
struct A { int x,y; };
struct B { int x,y; };
typedef A C;

int main() {
  std::cout << std::boolalpha;
  std::cout << "is_same:" << std::endl;
  std::cout << "int, const int: " << std::is_same<int, const int>::value << std::endl;
  std::cout << "int, integer_type: " << std::is_same<int, integer_type>::value << std::endl;
  std::cout << "A, B: " << std::is_same<A,B>::value << std::endl;
  std::cout << "A, C: " << std::is_same<A,C>::value << std::endl;
  std::cout << "signed char, std::int8_t: " << std::is_same<signed char,std::int8_t>::value << std::endl;
  return 0;
}

运行结果如下:

代码语言:javascript
复制
is_same:
int, const int: false
int, integer_type: true
A, B: false
A, C: true
signed char, std::int8_t: true

(2)is_base_of用法

is_base_of用来在编译期判断两种类型是否是继承关系,用法如下:

代码语言:javascript
复制
struct A {};
struct B : A {};

int main() {
  std::cout << std::boolalpha;
  std::cout << "is_base_of:" << std::endl;
  std::cout << "int, int: " << std::is_base_of<int,int>::value << std::endl;
  std::cout << "A, A: " << std::is_base_of<A,A>::value << std::endl;
  std::cout << "A, B: " << std::is_base_of<A,B>::value << std::endl;
  std::cout << "A, const B: " << std::is_base_of<A,const B>::value << std::endl;
  std::cout << "A&, B&: " << std::is_base_of<A&,B&>::value << std::endl;
  std::cout << "B, A: " << std::is_base_of<B,A>::value << std::endl;
  return 0;
}

程序运行结果如下:

代码语言:javascript
复制
is_base_of:
int, int: false
A, A: true
A, B: true
A, const B: true
A&, B&: false
B, A: false

(3)is_convertible用法

is_convertible是在编译器判断前面的模板参数是否能转换为后面的模板参数类型,用法如下:

代码语言:javascript
复制
struct A { };
struct B : A { };

int main() {
  std::cout << std::boolalpha;
  std::cout << "is_convertible:" << std::endl;
  std::cout << "int => float: " << std::is_convertible<int,float>::value << std::endl;
  std::cout << "int = >const int: " << std::is_convertible<int,const int>::value << std::endl;
  std::cout << "A => B: " << std::is_convertible<A,B>::value << std::endl;
  std::cout << "B => A: " << std::is_convertible<B,A>::value << std::endl;
  return 0;
}

程序运行结果如下:

代码语言:javascript
复制
is_convertible:
int => float: true
int => const int: true
A => B: false
B => A: true

(4)类型转换

常用的类型转换主要是对const类型的修改,引用的修改,数组的修改和指针的修改。涉及到的方法有很多,具体使用方法如下:

代码语言:javascript
复制
int main()  
{  
    //添加和移除const,referrence
    std::cout<<std::is_same<const int,std::add_const<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int,std::remove_const<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int&,std::add_lvalue_reference<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int,std::remove_reference<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int&&,std::add_rvalue_reference<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int,std::remove_reference<int>::type>::value<<std::endl;
    std::cout<<std::is_same<int*,std::add_pointer<int>::type>::value<<std::endl;
    //移除数组顶层维度
    std::cout<<std::is_same<int,std::remove_extent<int[]>::type>::value<<std::endl;
    std::cout<<std::is_same<int[2],std::remove_extent<int[][2]>::type>::value<<std::endl;
    //移除所有维度
    std::cout<<std::is_same<int,std::remove_all_extents<int[][2][3]>::type>::value<<std::endl;
    return 0;
}

1.3 根据条件选择traits

std::conditional在编译器根据条件来确定数据类型,形式形如一个三元运算符,如下:

代码语言:javascript
复制
template <bool Cond, class T, class F> struct conditional;

当表达式为真时类型是T,为假时类型是F。使用方法如下:

代码语言:javascript
复制
int main() {
  short int i = 1;    // code does not compile if type of i is not integral
  typedef std::conditional<true,int,float>::type A;                      // int
  typedef std::conditional<false,int,float>::type B;                     // float
  typedef std::conditional<std::is_integral<A>::value,long,int>::type C; // long
  typedef std::conditional<std::is_integral<B>::value,long,int>::type D; // int

  std::cout << std::boolalpha;
  std::cout << "typedefs of int:" << std::endl;
  std::cout << "A: " << std::is_same<int,A>::value << std::endl;
  std::cout << "B: " << std::is_same<int,B>::value << std::endl;
  std::cout << "C: " << std::is_same<int,C>::value << std::endl;
  std::cout << "D: " << std::is_same<int,D>::value << std::endl;
  return 0;
}

运行结果如下:

代码语言:javascript
复制
typedefs of int:
A: true
B: false
C: false
D: true

1.4 获取可调用对象返回类型的traits

std::result_of可以在编译器获取可调对象的返回类型,帮助解决编码过程中如下问题:

  • 函数入参为模板参数,不能直接确定函数返回类型;
  • 通过decltype推导函数返回类型时可读性差问题;
  • 使用后置推导类型时,如果没有构造函数导致编译报错的问题;

std::result_of原型如下:

代码语言:javascript
复制
template <class Fn, class... ArgTypes> 
struct result_of<Fn(ArgTypes...)>;

第一个模板参数为可调用的对象类型,第二个参数为参数类型,使用方法如下:

代码语言:javascript
复制
int fn(int) {return int();}                            // function
typedef int(&fn_ref)(int);                             // function reference
typedef int(*fn_ptr)(int);                             // function pointer
struct fn_class { int operator()(int i){return i;} };  // function-like class

int main() {
  typedef std::result_of<decltype(fn)&(int)>::type A;  // int
  typedef std::result_of<fn_ref(int)>::type B;         // int
  typedef std::result_of<fn_ptr(int)>::type C;         // int
  typedef std::result_of<fn_class(int)>::type D;       // int

  std::cout << std::boolalpha;
  std::cout << "typedefs of int:" << std::endl;

  std::cout << "A: " << std::is_same<int,A>::value << std::endl;
  std::cout << "B: " << std::is_same<int,B>::value << std::endl;
  std::cout << "C: " << std::is_same<int,C>::value << std::endl;
  std::cout << "D: " << std::is_same<int,D>::value << std::endl;
  return 0;
}

运行结果如下:

代码语言:javascript
复制
typedefs of int:
A: true
B: true
C: true
D: true

1.5 根据条件禁用或启用某些类型traits

先看如下代码:

代码语言:javascript
复制
template <typename T>
void Fun(T*){ std::cout<<"T*"<<std::endl;}

template <typename T>
void Fun(T){std::cout<<"T"<<std::endl;}

int main() {
  Fun(1);
  return 0;
}

程序运行为最终会匹配到第二个模板函数,但是在实际的匹配过程中,当匹配到void Fun(T*)时用整数对T*进行替换是错误的,但是编译器会继续匹配,直到匹配到void Fun(T)后执行正确的函数,这种规则就是SFINAE;反之,如果一个模板函数都没有匹配到,则编译器会报如下错误:

代码语言:javascript
复制
error: no matching function for call to 'Fun(int)'

std::enable_if实现了根据条件选择重载函数的规则,其原型如下:

代码语言:javascript
复制
template<bool Cond, class T = void> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; }

从上面定义可知,只有当表达式为true时才能生效,使用方法如下:

代码语言:javascript
复制
template <class T>
typename std::enable_if<std::is_integral<T>::value,bool>::type
  is_odd (T i) {return bool(i%2);}

template < class T,
           class = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even (T i) {return !bool(i%2);}

int main() {
  std::cout << std::boolalpha;
  std::cout << "i is odd: " << is_odd(2) << std::endl;
  std::cout << "i is even: " << is_even(3) << std::endl;
  return 0;
}

上面的代码std::enable_if 主要用作函数返回值,同时它还可以用来限定模板定义模板特化和入参类型限定。因此,它可以在编译期间检查模板参数是否有效。使用std::enable_if可以实现一个强大的重载机制,充分利用可以减少或者消除圈的复杂度。如:根据不同的数据基本类型转换为string进行输出。

代码语言:javascript
复制
template <class T>
std::string ToString(T t){
    if(typeid(T).name()==typeid(int).name() || 
    typeid(T).name() == typeid(double).name() || 
    typeid(T).name() == typeid(float).name())
    {
        std::stringstream ss;
        ss << t;
        return ss.str();
    }
    else if(typeid(T).name()==typeid(std::string).name())
    {
        return std::to_string(t);
    }
    return "null";
}

上面的代码为了实现基本数据类型的转换,圈复杂度直接上5,如果使用enable_if 则可以有效减少圈复杂度。实现方法如下:

代码语言:javascript
复制
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value,string>::type
ToString(const T& t){return std::to_string(t);}

template<class T>
typename std::enable_if<std::is_same<T,string>::value,string>::type
ToString(const T& t){return t;}

int main() {
  std::cout << ToString<double>(3.5) << std::endl;
  return 0;
}

上面代码运行结果为:

代码语言:javascript
复制
3.500000
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CPP开发前沿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档