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

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

traits classes 通过 templates 和 “templates 特化” 使得 “类型相关信息” 在编译期可用,通过重载技术(overloading)实现在编译期对类型执行 if…else 测试。


一、从一个实际问题出发

STL 中有一个非常有用的函数 advance,它的作用是将迭代器向前移动指定的距离:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);  // 将迭代器向前移动 d 个单位

这个函数看似简单,但实现起来却暗藏玄机。因为 STL 中的迭代器分为多种类型,不同类型的迭代器支持的操作截然不同:

迭代器类型 能力 示例容器
Input Iterator 只读,单次遍历,只能前进 istream_iterator
Output Iterator 只写,单次遍历,只能前进 ostream_iterator
Forward Iterator 可读写,多次遍历,只能前进 forward_list
Bidirectional Iterator 可读写,多次遍历,可前进后退 list, set, map
Random Access Iterator 可读写,支持随机访问(迭代器算术) vector, deque, array

对于 Random Access Iterator,我们可以用 iter += d 在 O(1) 时间内完成移动;但对于 Input IteratorForward Iterator,我们只能一步一步地 ++iter,需要 O(n) 时间。

问题来了:如何在编译期知道一个迭代器属于哪种类型,从而选择最优的实现?


二、迭代器标签(Iterator Tags)

C++ 标准库为这五种迭代器类型定义了对应的标签结构(tag struct):

struct input_iterator_tag {
   
   };
struct output_iterator_tag {
   
   };
struct forward_iterator_tag : public input_iterator_tag {
   
   };
struct bidirectional_iterator_tag : public forward_iterator_tag {
   
   };
struct random_access_iterator_tag : public bidirectional_iterator_tag {
   
   };

注意这里的继承关系!这种层次结构非常重要,它允许我们利用多态性来处理迭代器类型。

2.1 容器中的迭代器类型声明

每个 STL 容器的迭代器类都会内嵌一个 iterator_category 类型定义:

// deque 的迭代器是随机访问迭代器
template<...>
class deque {
   
   
public:
    class iterator {
   
   
    public:
        typedef random_access_iterator_tag iterator_category;
        // ...
    };
};

// list 的迭代器是双向迭代器
template<...>
class list {
   
   
public:
    class iterator {
   
   
    public:
        typedef bidirectional_iterator_tag iterator_category;
        // ...
    };
};

三、iterator_traits:类型信息的搬运工

3.1 为什么需要 traits?

你可能会想:直接在迭代器类里获取 iterator_category 不就行了?

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
   
   
    // 这样写有什么问题?
    if (typeid(typename IterT::iterator_category) == typeid(random_access_iterator_tag)) {
   
   
        iter += d;  // 编译错误!对于非随机访问迭代器,iter += d 不合法
    }
    // ...
}

致命缺陷

  1. if 判断在运行期执行,但两个分支在编译期都会被编译
  2. 如果 IterTlist<int>::iteratoriter += d 会导致编译失败
  3. 即使编译通过,运行期判断也带来不必要的开销

3.2 原生指针怎么办?

另一个关键问题:原生指针(如 int*)也是一种迭代器,但指针不是类,无法内嵌 typedef

3.3 iterator_traits 登场

iterator_traits 是标准库提供的 traits class,它统一了"类迭代器"和"原生指针"的类型信息获取方式:

// 通用版本:针对类迭代器
template<typename IterT>
struct iterator_traits {
   
   
    typedef typename IterT::iterator_category iterator_category;
    typedef typename IterT::value_type        value_type;
    typedef typename IterT::difference_type   difference_type;
    typedef typename IterT::pointer           pointer;
    typedef typename IterT::reference         reference;
};

// 偏特化版本:针对原生指针
template<typename T>
struct iterator_traits<T*> {
   
   
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凡人叶枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值