Item 47:使用 traits classes 表现类型信息

对齐 Effective C++ 第三版 Item 47:用 iterator_traits 主模板与 pointer partial specialization 统一获取类型信息,以 iterator category tags 对 advance 做编译期分派,并验证能力、复杂度和扩展边界。

学习目标

  • 能解释 traits class 如何在不增加对象运行状态的前提下统一提供 class 与 raw pointer 的类型信息
  • 能实现 iterator_traits 主模板、pointer partial specialization 和五类 iterator category tags
  • 能设计 tag dispatch 版 advance,并验证不同能力的操作、负距离规则与复杂度保证
Traits information / dispatch maptype facts → non-intrusive facade → capability overloadinformation about typescategory / value无 runtime statetraits classesprimary + specializations统一查询入口iterator_traitsIter / T*pointer adaptationtag dispatchoverload setcomplexity pathcapability fact must be true, not merely compilableinput: ++bidirectional: --random: +=measure operationstraits classes 描述能力,tag dispatch 消费能力;复杂度承诺也属于类型契约
traits 把类型事实集中到统一 facade,tag dispatch 再把事实转成编译期 overload 选择;错误 category 会造成复杂度契约破坏。

类型事实实验

先预测:这个 iterator 能保证什么?

先预测 traits 来源、category 和复杂度,再切换场景查看静态分派证据。

观察

泛型算法需要 category、value_type、difference_type 等 information about types,但 raw pointer 没有 nested members。

决策

用 traits classes 作为统一查询 facade;让 class iterator 由 primary template 提供信息,非侵入目标由 specialization 适配。

当前场景 · traits classes / information about types

用 traits classes 作为统一查询 facade;让 class iterator 由 primary template 提供信息,非侵入目标由 specialization 适配。

从“iterator 属于哪一类”开始

generic advance 需要把 iterator 向前或向后移动 distance。不同 iterator 能力差异很大:

  • input iterator 只能逐步前进
  • bidirectional iterator 可逐步前进和后退
  • random-access iterator 可直接 += distance

Item 47 的原则是 Use traits classes for information about types(使用 traits classes 表现类型信息)。

若算法只写循环,可支持所有 iterators,却浪费 random-access 的常量时间能力;若无条件写 +=,input iterator 无法编译。

先预测:运行期 if 检查 category 是否足够?两个 branches 仍需对当前 iterator 都是可编译 expressions,且类型事实本来在编译期已知。

iterator class 可以提供 nested category

struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : input_iterator_tag {};
struct bidirectional_iterator_tag : forward_iterator_tag {};
struct random_access_iterator_tag : bidirectional_iterator_tag {};
 
template<class T>
class ListIterator {
public:
    using iterator_category = bidirectional_iterator_tag;
};

category tag 不是 enum runtime value,而是一个 type。继承层级表达能力包含:random-access 也满足 bidirectional、forward 和 input 的基本要求。

raw pointer 也是 iterator,但没有成员

int values[] = {1, 2, 3};
int* iter = values;
iter += 2;

pointer 支持 random access,却不能为 int* 增加 iterator_category nested type。如果算法直接写 Iter::iterator_category,raw pointers 被排除。

解决方案是把查询移到独立 traits template。

iterator_traits 主模板

template<class Iter>
struct iterator_traits {
    using iterator_category = typename Iter::iterator_category;
    using value_type = typename Iter::value_type;
    using difference_type = typename Iter::difference_type;
    using pointer = typename Iter::pointer;
    using reference = typename Iter::reference;
};

generic code 只写 iterator_traits<Iter>::iterator_category,不关心信息来自 iterator 自身还是 specialization。

pointer partial specialization

template<class T>
struct iterator_traits<T*> {
    using iterator_category = random_access_iterator_tag;
    using value_type = std::remove_cv_t<T>;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using reference = T&;
};

iterator_traits<int*> 选择 pointer specialization,iterator_traits<ListIterator<int>> 选择 primary template,两者提供相同查询 names。

tag dispatch 的三个实现

template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, random_access_iterator_tag) {
    iter += n;
}
 
template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, bidirectional_iterator_tag) {
    if (n >= 0) while (n--) ++iter;
    else while (n++) --iter;
}
 
template<class Iter, class Distance>
void doAdvance(Iter& iter, Distance n, input_iterator_tag) {
    if (n < 0) throw std::out_of_range("negative distance");
    while (n--) ++iter;
}

forward iterator 的 tag 可转换为 input tag,random-access tag 也可转换到更弱 tags;overload resolution 选择最具体 exact match。

public wrapper 获取 trait 并分派

template<class Iter, class Distance>
void advance(Iter& iter, Distance n) {
    using Category = typename iterator_traits<Iter>::iterator_category;
    doAdvance(iter, n, Category{});
}

客户只调用 advance;category 在编译期选择实现,没有 per-call virtual dispatch 或 runtime branch。

tag hierarchy 是能力关系,不只是名字列表

forward tag public 继承 input tag,bidirectional 继承 forward,random-access 继承 bidirectional。这让只提供弱能力 overload 的算法也能接受更强 iterator。

output iterator 与 input 链表达不同写入语义,不应为了方便强行纳入同一可读层级。

若一个 iterator 宣称 random-access 却让 += 线性执行,程序可能结果正确但违反算法复杂度假设。

traits 不限于 types

traits 还可提供 compile-time constants:

template<class T>
struct serialization_traits {
    static constexpr bool fixed_size = false;
    using wire_type = T;
};

标准库中的 numeric_limitsallocator_traitschar_traitspointer_traits 都体现相似模式:一个统一查询 facade,背后由主模板和 specializations 提供事实。

用户 specialization 的所有权边界

并非所有 traits 都允许用户对任意标准类型添加 specialization。必须遵守该 traits 的定制契约;对自己拥有的 user-defined type 通常最安全。

自定义 traits 应说明:primary template 是否有默认、缺少信息时报何种错误、用户 specialization 必须提供哪些 members。

现代 concepts 与 if constexpr

现代 ranges 使用 iterator concepts;算法可写:

template<class Iter, class Distance>
void modernAdvance(Iter& iter, Distance n) {
    if constexpr (std::random_access_iterator<Iter>) {
        iter += n;
    } else if constexpr (std::bidirectional_iterator<Iter>) {
        // loop both directions
    } else {
        // input path
    }
}

concepts 可直接检查 operations,减少类型谎报;但关联 types、policy constants 和第三方适配仍常通过 traits 提供。Item 47 的信息抽取与静态选择模型没有过时。

traits class 的设计流程

  1. 识别 generic algorithm 需要的类型事实。
  2. 为每项事实确定稳定 nested name 或 constant。
  3. 编写 primary template,决定转发、自省或无默认。
  4. 为 raw pointers 等不能自描述的类型提供 specializations。
  5. 用 tag overload、if constexpr 或 specialization 消费信息。
  6. 测试 class types、pointers、cv variants、边界操作与复杂度。

先预测 custom iterator、const pointer 和 input-only iterator 分别选择哪个 overload,再用 operation counter 验证 random-access 只执行一次 +=

小结

  • traits classes 在编译期提供关于 types 的统一信息,不需要修改目标类型或增加运行状态
  • iterator_traits 主模板读取 class nested members,pointer partial specialization 适配 raw pointers
  • category tags 用类型和继承表达 iterator capability hierarchy
  • public advance 查询 trait 后进行 tag dispatch,在编译期选择最具体实现
  • category 不只影响可编译性,还承诺 operations、multi-pass 和复杂度
  • concepts 与 if constexpr 是现代消费方式,traits 仍适合关联类型、常量和非侵入适配

资料与写作方式声明

本章以Effective C++, Third Edition, Item 47权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

名词解释

本章出现的专业名词,用大白话再讲一遍。

type information
泛型算法查询的类型属性。
traits class

把类型映射为关联类型或常量的模板。

iterator category tag

表示 iterator 能力的空类型。

nested trait member

类型自身公开的关联属性名称。

non-intrusive trait target

无法自行添加 nested members 的类型。

traits primary template

处理常规类型的默认 traits 定义。

partial specialization

为一族模板实参形状提供的具体定义。

non-intrusive adaptation

不修改目标类型的 traits 适配。

tag dispatch

用 tag 参数和重载选择静态实现。

capability-specific overload

针对某类能力优化的内部函数。

traits dispatch wrapper

查询 trait 并调用实现的公开薄层。

capability tag hierarchy

强能力 tag 可转为弱能力 tag 的层级。

category semantic contract

tag 对操作和复杂度的真实承诺。

trait value member

traits 暴露的编译期常量。

trait specialization ownership

traits 定制的类型所有权边界。

if constexpr dispatch

按静态条件丢弃分支的选择。

capability concept

直接描述有效操作的命名约束。

traits design workflow

类型信息建模与消费流程。

练习

  1. 问题 1:traits classes 与 information about types。 让 advance 同时接受 ListIterator 和 raw pointer,两者分别是 bidirectional 与 random-access。
  1. 问题 2:iterator_traits 与 tag dispatch 的复杂度契约。 一个 iterator 把自己标成 random-access,但加等操作线性循环,分析后果与修复。
  1. 问题 3:traits classes、iterator_traits 与 tag dispatch 的非侵入适配。 为第三方不可修改 handle 类型提供 serialization trait,需要 wire type 与 fixed-size 常量。

讨论

评论区加载中…