Última actualización:2026-10-12
STL traits extracción de tipos¶
0.Introducción¶
En la programación con STL, contenedores y algoritmos se diseñan de forma independiente, es decir, las estructuras de datos y los algoritmos se diseñan por separado; el puente que conecta contenedores y algoritmos son los iteradores, que hacen posible ese diseño independiente. Como muestra la imagen:

La imagen anterior muestra que el objetivo de STL es separar los datos de los algoritmos, diseñar cada parte por separado y luego, mediante algo llamado iterator, volver a unir ambas.
En los patrones de diseño, el iterator se describe así: un método capaz de acceder secuencialmente a cada elemento de un contenedor sin exponer la representación interna del contenedor. Y la técnica de extracción de tipos existe precisamente para resolver los problemas relacionados con los iteradores.
Al aplicar los algoritmos genéricos (find, count, find_if) sobre un contenedor, lo más importante es proporcionar al algoritmo una herramienta para acceder a los elementos del contenedor, y el iterator desempeña ese papel crucial.
Dentro de los algoritmos es posible que definamos variables intermedias sencillas o establezcamos el tipo de la variable de retorno del algoritmo; para eso hay que saber cuál es el tipo del elemento apuntado por el iterador, pero al no existir una función tipo typeof que determine tipos, no podemos obtenerlo directamente: ¿qué hacemos entonces? Eso es lo que este artículo explica en detalle.
Un iterador es una especie de puntero inteligente y, por tanto, posee todas las características de un puntero normal: se le pueden aplicar las operaciones * y ->. Pero al recorrer un contenedor resulta inevitable conocer algo del interior del contenedor recorrido, así que, mejor todavía, dejemos el desarrollo del iterador en manos del diseñador del contenedor; de este modo todos los detalles de implementación quedan encapsulados y ocultos al usuario, y esa es exactamente la razón de que cada contenedor de STL proporcione su propio iterador exclusivo.
Y Traits está en 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;
};
¿Lo miras con cara de no entender nada? No pasa nada: cuando termines esta sección habrás entrado en STL, ja~
1.Deducción de parámetros template¶
Primero, al usar iteradores en los algoritmos, es muy probable que se necesite su tipo asociado (associated type) (el tipo de aquello que el iterador apunta). Supongamos que en un algoritmo es necesario declarar una variable cuyo tipo sea "el tipo del objeto apuntado por el iterador": ¿qué hacemos entonces?
La solución es: usar el mecanismo de deducción de parámetros de los function template.
Por ejemplo:
Si T es un puntero a un objeto determinado, ¿qué hacer cuando en func se necesita el tipo del objeto apuntado por el puntero? Esto aún es bastante fácil: el mecanismo de deducción de parámetros de las plantillas puede encargarse de la tarea.
template <class I>
inline
void func(I iter) {
func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导
}
Mediante el mecanismo de deducción de las plantillas obtenemos sin esfuerzo el tipo del objeto apuntado por el puntero.
template <class I, class T>
void func_impl(I iter, T t) {
T tmp; // 这里就是迭代器所指物的类别
// ... 功能实现
}
int main() {
int i;
func(&i);
}
Pero el "mecanismo de deducción de parámetros template" de las funciones solo deduce parámetros, no puede deducir el tipo de retorno de una función. Si hubiera que deducir el valor devuelto por la función, se quedaría corto. Por eso pasamos al método siguiente.
2.Declarar el tipo anidado¶
El tipo del objeto apuntado por el iterador se denomina value type del iterador.
Aunque en func_impl podríamos usar T como tipo de retorno de la función, el problema es que lo que el usuario necesita llamar es 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
}
Si compilas el código anterior, ¡la compilación falla!
Este problema tampoco es difícil de resolver: declarar el tipo anidado parece una buena idea, y así podríamos obtenerlo directamente. Basta con hacer un iterator y, al definirlo, darle un alias al tipo del objeto al que apunta, y listo; algo así:
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
}
Una solución preciosa, y a la vista todo parece perfecto. Pero en realidad sigue habiendo un problema: si func es un algoritmo genérico, tiene que aceptar también de forma absoluta un puntero nativo como iterador, pero está claro que no conseguirías que el código siguiente compile:
Nuestro func no admite punteros nativos, algo evidentemente inaceptable. Llegados aquí, la especialización parcial de templates (template partial specialization) entra en escena para salvar la situación.
3.El salvador: Traits¶
Como se mencionó antes, si se usa directamente typename I::value_type, el algoritmo no puede recibir punteros nativos, porque un puntero nativo no tiene en absoluto ese tipo anidado value_type.
Por tanto, hay que añadir una capa intermedia que determine si se trata de un puntero nativo; fijaos bien, aquí está la gracia de la técnica traits.
Si nos limitáramos a lo anterior, el value_type anidado, para los punteros sin value_type solo podríamos hacer una especialización parcial; esa especialización parcial sería de la función invocable func, y si func tuviera un millón de líneas de código, se produciría una enorme contaminación visual.
(1)Especialización parcial de funciones
Especialización parcial de funciones:
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;
}
Salida:
(2)Añadir la capa intermedia
¿Qué es Traits en STL? Mira la imagen siguiente:

Usando una capa intermedia iterator_traits se fija la forma de func, lo que reduce enormemente el código repetido; lo único que queda por hacer es especializar un poco iterator_traits para que admita pointer y 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;
}
El proceso anterior consiste en preguntar primero a iterator_traits<I>::value_type: si la I que se pasa es un puntero, se entra en la versión especializada y iterator_traits responde directamente; si la I que entra es un class type, se va a preguntar a T::value_type.
La explicación en lenguaje llano de lo anterior es: el algoritmo (func) me pregunta a mí (iterator_traits), pero cuando yo (iterator_traits) descubro que lo que tengo en la mano es un puntero, respondo yo en su lugar. Si es un class type, yo (iterator_traits) sigo preguntando (a él --- T::value_type).
Resumen: definiendo tipos anidados obtuvimos un método para conocer el tipo del elemento apuntado por un iterator; con la técnica traits unificamos las definiciones de la plantilla de función para punteros nativos y para iterators personalizados. Usamos la técnica traits principalmente para resolver la redundancia de código provocada por las diferencias entre los punteros nativos y los iterators personalizados: ahí está la gracia de la técnica traits.
Libro de estudio:
侯捷《 STL 源码剖析》
Artículos de estudio:
https://juejin.im/post/5b1a43fb51882513bf1795c6
Temas relacionados¶
- Análisis del código fuente de STL array | C++那些事
- Análisis profundo de STL string | C++那些事
- STL string preguntas de entrevista explicadas | C++那些事
- Análisis del código fuente de STL deque | C++那些事
- STL optimización de clase base vacía EBO | C++那些事
- STL implementación de una tabla hash escrita a mano | C++那些事
- STL hashtable tabla hash | C++那些事
- Análisis del código fuente de los iteradores STL | C++那些事
- Análisis del código fuente de STL list | C++那些事
- STL map y multimap | C++那些事