参考博文 https://blog.csdn.net/lihao21/article/details/55043881

Traits classes 的作用主要是用来为使用者提供类型信息。在 C++ 中,traits 习惯上总是被实现为 struct ,但它们往往被称为 traits classes。

为了清晰理解 traits 的原理,我们先来看 traits 使用的关键技术 —— 模板的特化与偏特化。

模板特化(Template Specialization)

所谓特化,就是将泛型的东西搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,受到特定的修饰,或完全被指定了下来。

我们先来看下一函数模板的通用定义:

template<typename T>
struct my_is_void {
static const bool value = false;
};

然后,针对 void 类型,我们有以以下的特化版本:

template<>
struct my_is_void<void> {
static const bool value = true;
};

测试代码如下:

my_is_void<bool> t1;
cout << t1.value << endl; // 输出0
my_is_void<void> t2;
cout << t2.value << endl; // 输出1

当声明 my_is_void<void> t2; 时,使用的是特化版本,故其 value 值为 1。

偏特化(Patial Spcialization)

偏特化就是只指定一部分而非所有模板参数,或者是参数的一部分而非全部特性。(模板函数只能全特化,没有偏特化)

template<typename T>
struct my_is_pointer {
static const bool value = false;
};

我们对模板参数T进行限制,要求其为一个指针的类型:

template<typename T>
struct my_is_pointer<T*> {
static const bool value = true;
};

测试:

my_is_pointer<int> p1;
cout << p1.value << endl; // 输出 0,使用原始模板
my_is_pointer<int*> p2;
cout << p2.value << endl; // 输出 1,使偏特化模板,因为指定了 int * 类型的参数

typename

以下模板的声明中, class 和 typename 有什么不同?

template<class T> class Test;
template<typename T> class Test;

答案是没有不同。但除此之外,C++ 并不总是把 class 和 typename 视为等价。有时候我们一定得使用 typename。

默认情况下,C++ 语言假定通过作用域运算符访问的名字不是类型。因此,如果你希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字 typename 来实现这一点:

template<typename T>
typename T::value_type top(const T &c)
{
if (!c.empty())
return c.back();
else
return typename T::value_type();//必须加typename
}

top 函数期待一个容器类型的实参,它使用 typename 指明其返回类型

测试代码:

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
cout << top<vector<int> >(vec) << endl; // 输出3

当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用 class。

Traits Classes

说完了背景知识,我们正式进入 traits

我们知道,在 STL 中,容器与算法是分开的,彼此独立设计,容器与算法之间通过迭代器联系在一起。那么,算法是如何从迭代器类中萃取出容器元素的类型的?没错,这正是我们要说的 traits classes 的功能。

迭代器所指对象的类型,称为该迭代器的 value_type。我们来简单模拟一个迭代器 traits classes 的实现。

template<class IterT>
struct my_iterator_traits {
typedef typename IterT::value_type value_type;
};

my_iterator_traits 其实就是个类模板,其中包含一个类型的声明。

对于my_iterator_traits,我们再声明一个偏特化版本。

template<class IterT>
struct my_iterator_traits<IterT*> {
typedef IterT value_type;
//即如果 my_iterator_traits 的实参为指针类型时,
//直接使用指针所指元素类型作为 value_type。
};

为了测试 my_iterator_traits 能否正确萃取迭代器元素的类型,我们先编写以下的测试函数。

void fun(int a) {
cout << "fun(int) is called" << endl;
} void fun(double a) {
cout << "fun(double) is called" << endl;
} void fun(char a) {
cout << "fun(char) is called" << endl;
}

我们通过函数重载的方式,来测试元素的类型。

测试代码如下:

my_iterator_traits<vector<int>::iterator>::value_type a;
fun(a); // 输出 fun(int) is called
my_iterator_traits<vector<double>::iterator>::value_type b;
fun(b); // 输出 fun(double) is called
my_iterator_traits<char*>::value_type c;
fun(c); // 输出 fun(char) is called

为了便于理解,我们这里贴出 vector 迭代器声明代码的简化版本:

template <class T, ...>
class vector {
public:
class iterator {
public:
typedef T value_type;
...
};
...
};

我们来解释 my_iterator_traits<vector<int>::iterator>::value_type a; 语句的含义:

vector<int>::iteratorvector<int> 的迭代器,该迭代器包含了 value_type 的声明,由 vector 的代码可以知道该迭代器的 value_type 即为 int 类型。

接着,my_iterator_traits<vector<int>::iterator> 会采用 my_iterator_traits 的通用版本,即 my_iterator_traits<vector<int>::iterator>::value_type 使用 typename IterT::value_type 这一类型声明,这里 IterT 为 vector<int>::iterator,故整个语句萃取出来的类型为 int 类型。

对 double 类型的 vector 迭代器的萃取也是类似的过程。

my_iterator_traits<char*>::value_type 则使用 my_iterator_traits 的偏特化版本,直接返回 char 类型。

由此看来,通过 my_iterator_traits ,我们正确萃取出了迭代器所指元素的类型。

总结一下我们设计并实现一个 traits class 的过程:

  1. 确认若干我们希望将来可取得的类型相关信息,例如,对于上面的迭代器,我们希望取得迭代器所指元素的类型;
  2. 为该信息选择一个名称,例如,上面我们起名为 value_type;
  3. 提供一个 template 和一组特化版本(例如,我们上面的 my_iterator_traits),内容包含我们希望支持的类型相关信息。

C++ Traits Classes的更多相关文章

  1. Effective C++ -----条款47:请使用traits classes表现类型信息

    Traits classes使得“类型相关信息”在编译期可用.它们以template和“templates特化”完成实现. 整合重载技术(overloading)后,traits classes有可能 ...

  2. Effective C++ Item 47 请使用 traits classes 表现类型信息

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:Traits classes 使得"类型相关信息"在编译期可用.它 ...

  3. 【47】请使用traits classes表现类型信息

    1.考虑下面的需求,对迭代器移动d个单位.因为不同类型的迭代器,能力不同,有的迭代器(vector,deque内置迭代器)可以一步到位移动到指定位置,有的迭代器(list内置迭代器)必须一步一步移动, ...

  4. 读书笔记_Effective_C++_条款四十七:请使用trait classes来表示类型信息

    这一条款主要来讨论模板中迭代器的属性iterator_category,它可以通过类似于vector<int>::iterator::iterator_category的方式来取得. 到这 ...

  5. 侯捷C++ Type traits(类型萃取

    泛型編程編出來的代碼,適用於任何「吻合某種條件限制」的資料型別.這已成為撰寫可復用代碼時的一個重要選擇.然而,總有一些時候,泛型不夠好 — 有時候是因為不同的型別差距過大,難以產生一致的泛化實作版本. ...

  6. C++中的Traits技法

    Traits广泛应用于标准程序库.Traits classes使得"类型相关信息"在编译期可用. 认真读完下面的示例,你应该就懂了Traits技法,其实并不难. #include ...

  7. 读书笔记 effective c++ Item 47 使用traits class表示类型信息

    STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板.其中之一叫做advance.Advance将一个指定的迭代器移动指定的距离: template<typename IterT ...

  8. 《Effective C++》再次探索traits技法

    首先介绍C++标准程序库中的五种迭代器,关于这个可以看我的另一个笔记:http://blog.csdn.net/m0_37316917/article/details/70053513. 对于这五种分 ...

  9. Beginning Scala study note(3) Object Orientation in Scala

    1. The three principles of OOP are encapsulation(封装性), inheritance(继承性) and polymorphism(多态性). examp ...

  10. 《Effective C++》第三版笔记

    阅读此笔记前,请先阅读 <Effective C++>第二版笔记  和  <More Effective C++>笔记 这里只记录与上面笔记不同的条款,主要是 "面对 ...

随机推荐

  1. ProcessingJS

    ProcessingJS 图形 rect(x, y, w, h)(在新窗口中打开) ellipse(x, y, w, h) triangle(x1, y1, x2, y2, x3, y3) line( ...

  2. Appendix D. Gradle Command Line

    http://www.gradle.org/docs/current/userguide/gradle_command_line.html Appendix D. Gradle Command Lin ...

  3. linux的进阶命令

    一. linux的基础命令 1.history 查看历史命令记录 2.ifconfig 查看所在的IP地址 3.tail -n 查看一个文件的后n行记录 4.head -n 查看一个文件的前n行记录5 ...

  4. 造轮子之EventBus

    前面基础管理的功能基本开发完了,接下来我们来优化一下开发功能,来添加EventBus功能.EventBus也是我们使用场景非常广的东西.这里我会实现一个本地的EventBus以及分布式的EventBu ...

  5. 13. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法

    用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法 项目 ++wmproxy++ gite: https://gitee.com/ ...

  6. 拒绝恶意IP登录服务器

    拒绝恶意IP登录服务器,并加入防火墙黑名单 #!/bin/bash #2020-03-20 16:39 #auto refuse ip dlu #By Precious ############### ...

  7. 合唱队形(lgP1091)

    思路: 先从左到右求一遍最长不下降子序列,再同样方法从右到左求一遍. 然后我们枚举分界点,则总人数就是左边一半加上右边一半的人数. 取最大值,输出答案. 见注释. #include<bits/s ...

  8. Spring扩展接口(4):InstantiationAwareBeanPostProcessor

    在此系列文章中,我总结了Spring几乎所有的扩展接口,以及各个扩展点的使用场景.并整理出一个bean在spring中从被加载到最终初始化的所有可扩展点的顺序调用图.这样,我们也可以看到bean是如何 ...

  9. Spring/SpringBoot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战

    一.前言 在现代软件开发中,事务处理是必不可少的一部分.当多个操作需要作为一个整体来执行时,事务可以确保数据的完整性和一致性,并避免出现异常和错误情况.在SpringBoot框架中,我们可以使用声明式 ...

  10. Linux TTY/PTS

    转载:https://segmentfault.com/a/1190000009082089 当我们在键盘上敲下一个字母的时候,到底是怎么发送到相应的进程的呢?我们通过ps.who等命令看到的类似tt ...