参考博文 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. ElasticSearch系列——文档操作

    文章目录 Elasticsearch的增删查改(CURD) 一 CURD之Create 二 CURD之Update 三 CURD之Delete 四 CURD之Retrieve Elasticsearc ...

  2. Redis系列之——Redis-Cluster

    文章目录 一 Redis Cluser介绍背景 1.1问题 1.2 解决 二 数据分布(分布式数据库) 2.1 存在问题 2.2 分区方式 2.2.1 顺序分区 2.2.2 哈希分区 2.2.2 .1 ...

  3. umich cv-5-2 神经网络训练2

    这节课中介绍了训练神经网络的第二部分,包括学习率曲线,超参数优化,模型集成,迁移学习 训练神经网络2 学习率曲线 超参数优化 模型集成 迁移学习 学习率曲线 在训练神经网络时,一个常见的思路就是刚开始 ...

  4. dicker 常用命令(简洁版)

  5. AirSim 自动驾驶仿真 (6) 设置采集参数和属性

    https://cloud.tencent.com/developer/article/2011384 1.配置文件在哪 默认情况下,文件位于用户目录下的AirSim文件夹,比如在Windows下,文 ...

  6. 【NOI2014】 魔法森林---解题报告

    传送门 题目大意 给定 \(n\) 个点和 \(m\) 条边.每条边包含起点终点和两个精灵的最低限制,求最少需要携带的精灵数量. 题目解析 直接套 LCT 板子 将所有边按照进行升序排序,从小到大将边 ...

  7. 升级到 Pulsar3.0 后深入了解 JWT 鉴权

    背景 最近在测试将 Pulsar 2.11.2 升级到 3.0.1的过程中碰到一个鉴权问题,正好借着这个问题充分了解下 Pulsar 的鉴权机制是如何运转的. Pulsar 支持 Namespace/ ...

  8. 普冉PY32系列(十二) 基于PY32F002A的6+1通道遥控小车III - 驱动篇

    目录 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU简介 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境 普冉PY32系列(三) P ...

  9. Kotlin协程系列(二)

    在进行业务开发时,我们通常会基于官方的协程框架(kotlinx.coroutines)来运用Kotlin协程优化异步逻辑,不过这个框架过于庞大和复杂,如果直接接触它容易被劝退.所以,为了我们在后续的学 ...

  10. Android 实现APP可切换多语言

    原文: Android 实现APP可切换多语言 - Stars-One的杂货小窝 如果是单独给app加上国际化,其实很容易,创建对应的国家资源文件夹即可,如values-en,values-pt,ap ...