参考博文 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. 前端三件套系例之JQuery——JQuery基础、JQuery选择器、JQuery文本属性样式操作、JQuery操作DOM

    文章目录 1 JQuery基础 1. 了解jQuery 1.1 什么是jQuery 1.2 什么事JS类库 1.3 常见JS类库 1.4 jQuery的优势 1.5 jQuery的版本 1.6 jQu ...

  2. Python网络编程——TCP套接字通信、通信循环、链接循环、UDP通信

    文章目录 基于TCP的套接字通信 加上通信循环 加上链接循环 基于UDP协议的套接字通信 基于TCP的套接字通信 以买手机的过程为例 服务端代码 import socket # 1.买手机 phone ...

  3. Molecule 在构建工具中的选择

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:修能 朝闻道,夕死可矣 何为 Molecule? 轻量级的 ...

  4. 在 windows 借助 git 修改文件权限

    起因: 创建新仓库的时候,仓库都没有脚本,然后就需要把运维同事的脚本复制过来,可执行脚本都是在 scripts 文件夹下面的 过了几天,来活了 刚开始还不知道咋回事,想到的文件权限是在linux系统下 ...

  5. js/javaScript实现金额千分位

    作为前端开发,我们都知道,在实际的需求开发中,难免会遇到需要将接口返回的金额进行千分位格式化的场景.千分位后的金额便于阅读,提升用户体验.金额千分位可以由前端来处理,也可以后端处理后返回给前端展示. ...

  6. 3.1 IDA Pro编写IDC脚本入门

    IDA Pro内置的IDC脚本语言是一种灵活的.C语言风格的脚本语言,旨在帮助逆向工程师更轻松地进行反汇编和静态分析.IDC脚本语言支持变量.表达式.循环.分支.函数等C语言中的常见语法结构,并且还提 ...

  7. sizeof结构体数组指针和sizeof数组指针的区别

    请思考一下 以下代码输出的 sizeof 分别是多少? #include <stdio.h> typedef struct { char name[100]; unsigned char ...

  8. Chinese Bank Card and Credit Card ID

    Regular match expression: [^0-9]((3|4|5|6|9)\d{15,18})[^0-9] Rule characteristics: first number:3 or ...

  9. 再学Blazor——组件建造者

    使用 RenderTreeBuilder 创建组件是 Blazor 的一种高级方案.前几篇文中有这样创建组件的示例 builder.Component<MyComponent>().Bui ...

  10. 向“创新者”升阶,程序员当下如何应对 AI 的挑战

    随着 AI 技术的飞速发展,特别是大模型的出现,传统的程序员角色正在经历深刻的变革,我们不得不重新对自己进行审视和思考. 通用领域大模型的"泛化能力" 在过去的二十年内,AI 领域 ...