Última atualização:2026-10-12
STL traits extração de tipos¶
0. Introdução¶
Na programação com a STL, contêineres e algoritmos são projetados de forma independente, ou seja, estruturas de dados e algoritmos são projetados separadamente; a ponte que conecta contêineres e algoritmos são os iteradores — são eles que tornam possível esse desenho independente. Conforme mostrado na figura abaixo:

A figura acima mostra que o objetivo da STL é separar dados e algoritmos, projetando cada um por si, e depois, por meio de algo chamado iterator, colar os dois de volta juntos.
Nos design patterns, a descrição do iterator é: um método que permite acessar sequencialmente cada elemento de um contêiner sem expor a representação interna do contêiner. E a técnica de extração de tipos (traits) existe justamente para resolver problemas relacionados a iteradores.
Ao aplicar algoritmos genéricos (find, count, find_if) a um contêiner, o mais importante é fornecer ao algoritmo uma ferramenta para acessar os elementos do contêiner, e é o iterator que desempenha esse papel essencial.
Nos algoritmos, podemos precisar definir variáveis intermediárias simples ou definir o tipo de retorno do algoritmo; nesses momentos é preciso conhecer o tipo do elemento apontado pelo iterador. Porém, como não existe uma função de verificação de tipo como typeof, não conseguimos obtê-lo diretamente — e então, o que fazer? É isso que este artigo explicará em detalhes.
Para os iteradores, trata-se de uma espécie de smart pointer; por isso, eles possuem todas as características de um ponteiro comum — é possível aplicar sobre eles as operações * e ->. Mas, ao percorrer um contêiner, é inevitável ter algum conhecimento do interior do contêiner percorrido; assim, é melhor deixar o desenvolvimento do iterador a cargo do projetista do contêiner. Com isso, todos os detalhes de implementação acabam encapsulados e ficam invisíveis para o usuário — e é exatamente por isso que cada contêiner da STL oferece seu próprio iterador dedicado.
E Traits está em bits/stl_iterator_base_types.h:
template<class _Tp>
struct iterator_traits<_Tp*>
{
typedef ptrdiff_t difference_type;
typedef typename _Tp::value_type value_type;
typedef typename _Tp::pointer pointer;
typedef typename _Tp::reference reference;
typedef typename _Tp::iterator_category iterator_category;
};
Parece confuso à primeira vista, não se preocupe: após terminar esta seção, você terá ingressado na STL, haha~
1. Dedução de parâmetros de template¶
Primeiro, ao usar iteradores em algoritmos, é bem provável que sejam necessários os seus tipos associados (associated types) (o tipo do objeto apontado pelo iterador). Suponha que o algoritmo precise declarar uma variável cujo tipo seja "o tipo do objeto apontado pelo iterador" — como proceder?
A solução é: usar o mecanismo de dedução de parâmetros dos function templates.
Por exemplo:
Se T é um ponteiro para determinado objeto, e dentro de func é preciso conhecer o tipo do objeto apontado pelo ponteiro, o que fazer? Isso é relativamente fácil: o mecanismo de dedução de parâmetros de templates dá conta da tarefa,
template <class I>
inline
void func(I iter) {
func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导
}
Por meio do mecanismo de dedução de templates, obtivemos com facilidade o tipo do objeto apontado pelo ponteiro.
template <class I, class T>
void func_impl(I iter, T t) {
T tmp; // 这里就是迭代器所指物的类别
// ... 功能实现
}
int main() {
int i;
func(&i);
}
Porém, o "mecanismo de dedução de parâmetros de template" de funções deduz apenas parâmetros; ele não consegue deduzir o tipo de retorno da função. Se for preciso deduzir o valor de retorno da função, ele não pode ajudar. Por isso, introduzimos o método a seguir.
2. Declarar tipos aninhados¶
O tipo do objeto apontado pelo iterador é chamado de value type do iterador.
Embora em func_impl possamos usar T como tipo de retorno da função, o problema é que o que o usuário chama é func.
template <class I, class T>
T func_impl(I iter, T t) {
T tmp; // 这里就是迭代器所指物的类别
// ... 功能实现
}
template <class T>
(*T) func(T t) { // !!!Wrong code
return func_impl(t, *t); // forward the task to func_impl
}
int main() {
int i =10;
cout<<func(&i)<<endl; // !!! Can’t pass compile
}
Se você compilar o código acima, a compilação falha!
Esse problema também não é difícil de resolver: declarar um tipo aninhado parece uma boa ideia, pois assim podemos obtê-lo diretamente. Basta fazer um iterator e, na definição, dar um alias ao tipo do objeto apontado, assim:
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
template <class I>
typename I::value_type
func(I ite) {
std::cout << "class version" << std::endl;
return *ite;
}
int main() {
// ...
MyIter<int> ite(new int(8));
cout << func(ite); // 输出8
}
Uma solução muito elegante — tudo parece perfeito. Mas, na verdade, ainda há um problema: se func é um algoritmo genérico, ele também deve aceitar, com toda a certeza, um ponteiro nativo como iterador; porém, evidentemente, você não consegue fazer o código abaixo compilar:
Nossa func não suporta ponteiros nativos, o que é claramente inaceitável. É aqui que a template partial specialization entra em cena.
3. O salvador Traits¶
Como mencionado antes, se usarmos diretamente typename I::value_type, o algoritmo não consegue receber ponteiros nativos, pois ponteiros nativos simplesmente não têm esse tipo aninhado value_type.
Por isso, precisamos acrescentar uma camada intermediária para verificar se se trata de um ponteiro nativo — e atenção: é aqui que está a beleza da técnica traits.
Se usássemos apenas a abordagem acima, isto é, o value_type aninhado, então para ponteiros sem value_type só poderíamos fazer uma especialização parcial — uma especialização parcial da própria função chamável func. Se func tivesse 1 milhão de linhas de código, isso causaria enorme poluição visual.
(1)Especialização parcial de funções
Especialização parcial de funções:
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
template <class I>
typename I::value_type
func(I ite) {
std::cout << "class version" << std::endl;
return *ite;
}
template <class I>
I
func(I* ite) {
std::cout << "pointer version" << std::endl;
return *ite;
}
template <class I>
I func(const I* ite) {
std::cout << "const pointer version" << std::endl;
return *ite;
}
int main() {
// ...
MyIter<int> ite(new int(8));
cout << func(ite)<<endl;
int *p = new int(52);
cout<<func(p)<<endl;
const int k = 3;
cout<<func(&k)<<endl;
}
Saída:
(2)Acrescentando a camada intermediária
O que é Traits na STL? Veja a figura abaixo:

Usando uma camada intermediária iterator_traits, fixamos a forma de func, o que reduz drasticamente o código repetido; tudo o que resta é especializar um pouco o iterator_traits para que suporte pointer e const pointer:)
#include <iostream>
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
// class type
template <class T>
struct iterator_traits {
typedef typename T::value_type value_type;
};
// 偏特化1
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};
// 偏特化2
template <class T>
struct iterator_traits<const T*> {
typedef T value_type;
};
template <class I>
typename iterator_traits<I>::value_type
// 首先询问iterator_traits<I>::value_type,如果传递的I为指针,则进入特化版本,iterator_traits直接回答;如果传递进来的I为class type,就去询问T::value_type.
func(I ite) {
std::cout << "normal version" << std::endl;
return *ite;
}
int main() {
// ...
MyIter<int> ite(new int(8));
std::cout << func(ite)<<std::endl;
int *p = new int(52);
std::cout<<func(p)<<std::endl;
const int k = 3;
std::cout<<func(&k)<<std::endl;
}
O processo acima primeiro pergunta a iterator_traits<I>::value_type: se o I passado é um ponteiro, entra-se na versão especializada e o próprio iterator_traits responde T; se o I passado é um class type, vai-se perguntar a T::value_type.
Em linguagem simples: o algoritmo (func) pergunta ao iterator_traits (a mim); mas quando o iterator_traits (eu) percebe que tem em mãos um ponteiro, respondo eu por ele. Se for um class type, o iterator_traits (eu) continua perguntando (a ele --- T::value_type).
Resumo: ao definir tipos aninhados, obtivemos um meio de conhecer o tipo do elemento apontado por um iterator; com a técnica traits, unificamos a definição do template de função tanto para ponteiros nativos quanto para iteradores personalizados. Usamos a técnica traits principalmente para eliminar a redundância de código causada pela diferença entre ponteiros nativos e iteradores personalizados — eis a beleza da técnica traits.
Livro de estudo:
侯捷《STL源码剖析》
Artigos de estudo:
https://juejin.im/post/5b1a43fb51882513bf1795c6
Tópicos relacionados¶
- Análise do código-fonte do STL array | C++那些事
- Análise aprofundada do STL string | C++那些事
- Questões de entrevista sobre STL string explicadas | C++那些事
- Análise do código-fonte do STL deque | C++那些事
- STL otimização de classe base vazia EBO | C++那些事
- STL implementação manual de tabela hash | C++那些事
- STL hashtable tabela hash | C++那些事
- Análise do código-fonte dos iteradores da STL | C++那些事
- Análise do código-fonte do STL list | C++那些事
- STL map e multimap | C++那些事