boost源码剖析之:泛型编程精灵type_traits(rev#2)

本文详细介绍了Boost库中的type_traits,包括其动机、应用以及如何在C++中实现。通过类型分派、效率优化和编译期检查等功能,type_traits提供了一种强大的工具来处理各种类型。文章深入探讨了如何使用type_traits进行类型检查、效率提升和代码兼容性增强,并分析了其在boost库中的实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

boost源码剖析之:泛型编程精灵type_traits(rev#2)

 

刘未鹏

C++的罗浮宫(http://blog.youkuaiyun.com/pongba)

 

动机

使用traits的动机一般有三种,分派、效率、使某些代码通过编译。

 

分派

下面有一个模板函数,假设一个动物收容组织提供了它,他们接受所有无家可归的可怜的小动物,于是他们向外界提供了一个函数接受注册。函数看起来像这样:

 

template<class T> // T表示接受的是何种动物

void AcceptAnimals(T animal)

{

...  //do something

};

 

但是,如果他们想将猫和狗分开处理(毕竟饲养一只猫和饲养一只狗并不相同。他们可能会为狗买一根链子,而温顺的猫则可能不需要)。一个可行的方法是分别提供两个函数:AcceptDogAcceptCat,然而这种解决办法并不优雅(想想看,注册者可能既有一只猫又有一只狗,这样他不得不调用不同的函数来注册,而且,如果种类还在增多呢,那样会导致向外提供的接口的增多,注册者因此而不得不记住那些烦琐的名字,而这显然没有只需记住AccpetAnimal这一个名字简单)。如果想保持这个模板函数,并将它作为向外界提供的唯一接口,则我们需要某种方式来获取类型T的特征(trait),并按照不同的特征来采用不同的策略。这里我们有第二个解决办法

 

约定所有的动物类(class Cat,class Dog)都必须在内部typedef一个表明自己身份的类型,作为标识的类型如下:

 

struct cat_tag{}; //这只是个空类,目的是激发函数重载,后面会解释

struct dog_tag{}; //同上

 

于是,所有狗类都必须像这样:

 

class Dog

{

public:

  // 类型(身份)标志,表示这是狗类,如果是猫类则为typedef cat_tag type;

typedef  dog_tag  type;

  ...

}

 

然后,动物收容组织可以在内部提供对猫狗分开处理的函数,像这样:

 

// 第二个参数为无名参数,只是为了激发函数重载

template<class T>

void Accept(T dog,dog_tag)

{...}

 

template<class T>

void Accpet(T cat,cat_tag) // 同上

{...}

 

 

于是先前的Accept函数可以改写如下:

 

template<class T>

void Accept(T animal)  //这是向外界提供的唯一接口

{

// 如果T为狗类,则typename T::type就是dog_tag,那么typename T::type()就是创建了一个dog_tag类的临时对象,根据函数重载的规则,这将调用Accept(T,dog_tag),这正是转向处理狗的策略。如果T为猫类,则typename T::typecat_tag,由上面的推导,这将调用Accept(T,cat_tag),即转向处理猫的策略,typename 关键字告诉编译器T::type是个类型而不是静态成员。

Accept(animal, typename T::type()); // #1

}

 

所有类型推导,函数重载,都在编译期完成,你几乎不用耗费任何运行期成本(除了创建dog_tag,cat_tag临时对象的成本,然而经过编译器的优化,这种成本可能也会消失)就拥有了可读性和可维护性高的代码。但是,等等!你说:“traits在哪?typename T::type其实就是traits,只不过少了一层封装而已,如果像这样作一些改进:

 

template<typename T>

struct AnimalTraits

{

typedef T::type type;

};

 

于是,#1处的代码便可以写成:

 

Accept(animal, typename AnimalTraits<T>::type());

 

效率

通常为了提高效率,为某种情况采取特殊的措施是必要的,例如STL里面的copy,原型像这样:

 

// [first,last)区间内的元素拷贝到以dest开始的地方

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest){

  // ptr_category用来萃取出迭代器的类别以进行适当程度的优化

return copy_opt(first,last,dest, ptr_category(first,dest));

}

 

copy_opt有两个版本,其中一个是针对如基本类型的数组作优化的,如果拷贝发生在char数组间,那么根本用不着挨个元素赋值,基于数组在内存中分布的连续性,可以用速度极快的memmove函数来完成。ptr_category有很多重载版本,对可以使用memmove的情况返回一个空类如scalar_ptr的对象以激发函数重载。其原始版本则返回空类non_scalar_ptr的对象。copy_opt的两个版本于是像这样:

 

// 使用memmove

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest,

scalar_ptr)

{ ...}

 

// 按部就班的逐个拷贝

template<typename IterIn,typename IterOut>

IterOut copy(IterIn first,IterIn last,IterOut dest,

 non_scalar_ptr)

{ ...}

 

其实通常为了提高效率,还是需要分派。

 

使某些代码能通过编译

这或许令人费解,原来不能通过编译的代码,经过traits的作用就能编译了吗?是的,考虑std::pair的代码(为使代码简洁,忽略大部分)

 

template <typename T1, typename T2>

struct pair

{

T1 first;

  T2 second;

 

// 如果T1T2本身是引用,则编译错误,因为没有“引用的引用

pair(const T1 & nfirst, const T2 & nsecond) // #2

:first(nfirst), second(nsecond) { } 

};

 

这里可以使用一个traits(boost库里面的名字为add_reference)来避免这样的错误。这个traits内含一个typedef,如果add_reference<T>T为引用,则typedef T type;如果不是引用,则typedef T& type;这样#2处的代码便可改成:

 

pair(add_reference<const T1>::type nfirst,

add_reference<const T2>::type nsecond)

  ...

 

这对所有的类型都能通过编译。

 

boost库中的traits

boost中的Traits十分完善,可分为如下几大类:

`std::ifstream::traits_type::eof()` 是 C++ 标准库中用于表示“**文件结束符**”(End of File)的一个特殊值。 --- ## ✅ 含义详解 ### 📌 `std::ifstream::traits_type::eof()` - **作用**:返回一个表示“文件结束”的特殊值。 - **类**:它是一个整数类(通常是 `int`)。 - **值**:通常定义为 `-1`,但这个值的具体实现取决于编译器和平台。 ### 🔧 用法示例: ```cpp std::ifstream ifs("data.txt"); if (ifs.peek() == std::ifstream::traits_type::eof()) { std::cout << "文件为空或已经读到末尾" << std::endl; } ``` --- ## ✅ 为什么不能写成 `ifs.peek() == ifs.eof()`? 你可能看到有些代码错误地写成了: ```cpp if (ifs.peek() == ifs.eof()) // ❌ 错误写法 ``` 这是**类不匹配的错误**: | 表达式 | 类 | 描述 | |--------|------|------| | `ifs.peek()` | `int_type`(通常是 `int`) | 下一个字符的值,或 EOF | | `ifs.eof()` | `bool` | 流是否到达 EOF(布尔值) | 所以你是在比较一个 `int` 和一个 `bool`,这在逻辑上是不成立的。 --- ## ✅ 正确比较方式 你应该使用: ```cpp if (ifs.peek() == std::ifstream::traits_type::eof()) ``` - `traits_type` 是 `std::ifstream` 的一个类别名,通常等价于 `std::char_traits<char>`。 - `traits_type::eof()` 是一个静态成员函数,用来返回 EOF 的表示值。 --- ## ✅ 举个例子 ```cpp #include <iostream> #include <fstream> int main() { std::ifstream ifs("example.txt"); if (!ifs) { std::cerr << "无法打开文件" << std::endl; return 1; } int next_char = ifs.peek(); if (next_char == std::ifstream::traits_type::eof()) { std::cout << "文件为空或已读到末尾" << std::endl; } else { std::cout << "下一个字符是: " << static_cast<char>(next_char) << std::endl; } ifs.close(); return 0; } ``` --- ## ✅ `traits_type::eof()` 的返回类 | 表达式 | 返回类 | 值示例 | |--------|-----------|--------| | `std::ifstream::traits_type::eof()` | `std::ifstream::traits_type::int_type` | 通常是 `-1` | 你也可以用 `int_type` 类别名来简化写法: ```cpp using int_type = std::ifstream::traits_type::int_type; int_type eof = std::ifstream::traits_type::eof(); ``` --- ## ✅ 小结 | 表达式 | 类 | 含义 | |--------|------|------| | `peek()` | `int_type` | 查看下一个字符(不提取) | | `traits_type::eof()` | `int_type` | 表示 EOF 的特殊值 | | `ifs.eof()` | `bool` | 是否已经到达文件末尾 | --- ###
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值