Plantillas C++ en .cpp: cómo no duplicar código y mantener la cordura

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Plantillas C++: por qué su código no enlaza (y cómo solucionarlo)

\n

¿Alguna vez ha escrito una elegante función plantilla, colocó cuidadosamente la declaración en .h, la definición en .cpp, compiló el proyecto... y obtuvo el temido error del enlazador? undefined reference — un saludo desde 1998. ¿Le suena familiar? Si es así, ha llegado al lugar indicado. Si no, o es un genio o nunca ha intentado separar plantillas. Bienvenido al club.

\n

En este artículo analizaremos una de las preguntas más populares de Stack Overflow (sí, esa con miles de visitas): «Storing C++ template function definitions in a .CPP file». No solo copiaremos la respuesta — entenderemos por qué funciona así, y aprenderemos a aplicarlo en la práctica. ¡Allá vamos!

\n\n

El problema: las plantillas no son funciones normales

\n

Imagine que está escribiendo una biblioteca en C++ (o simplemente un proyecto educativo para PythonLib). Tiene una función plantilla max:

\n
// max.h\ntemplate <typename T>\nT max(T a, T b);
\n
// max.cpp\ntemplate <typename T>\nT max(T a, T b) {\n    return (a > b) ? a : b;\n}
\n
// main.cpp\n#include "max.h"\nint main() {\n    int result = max(3, 7);  // ¡Error del enlazador!\n    return 0;\n}
\n

El compilador de main.cpp solo ve la declaración en .h. Genera una llamada a la función max<int>. ¿Pero dónde está su cuerpo? En max.cpp — pero el compilador no sabe que se necesitará int! Compila max.cpp por separado y, al no ver ninguna llamada a max<int>, simplemente no crea esa especialización. El resultado es un error en la fase de enlace.

\n

Idea clave: una plantilla no es código, es un plano del código. Hasta que el compilador no vea un tipo concreto, no generará código máquina. Por eso, la separación clásica en .h y .cpp para plantillas no funciona de serie.

\n\n

Solución #1: Todo en el archivo de cabecera (Header-Only)

\n

El enfoque más simple y usado frecuentemente es colocar tanto la declaración como la definición en el archivo .h. Sí, esto aumenta el tiempo de compilación (cada .cpp que incluya esta cabecera volverá a instanciar la plantilla), pero no hay problemas de enlace.

\n
// max.h (header-only)\n#ifndef MAX_H\n#define MAX_H\n\ntemplate <typename T>\nT max(T a, T b) {\n    return (a > b) ? a : b;\n}\n\n#endif
\n

Ventajas: simplicidad, sin errores de enlace, ideal para proyectos pequeños o bibliotecas donde las plantillas se usan con poca frecuencia.

\n

Desventajas: inflación del código en la compilación, aumento del tiempo de construcción, imposibilidad de ocultar la implementación (si distribuye la biblioteca como binario compilado).

\n\n

Solución #2: Instanciación explícita (Explicit Instantiation)

\n

Y aquí está el héroe de nuestro artículo. Si sabe de antemano con qué tipos se usará la plantilla, puede pedir explícitamente al compilador que cree esas especializaciones en el archivo .cpp. Esta es la respuesta a la pregunta «Storing C++ template function definitions in a .CPP file».

\n
// max.h\n#ifndef MAX_H\n#define MAX_H\n\ntemplate <typename T>\nT max(T a, T b);\n\n// Indicamos al enlazador que las especializaciones estarán en otro lugar\nextern template int max(int, int);\nextern template double max(double, double);\n\n#endif
\n
// max.cpp\n#include "max.h"\n\ntemplate <typename T>\nT max(T a, T b) {\n    return (a > b) ? a : b;\n}\n\n// Instanciamos explícitamente los tipos necesarios\ntemplate int max(int, int);\ntemplate double max(double, double);
\n
// main.cpp\n#include "max.h"\nint main() {\n    int a = max(3, 7);      // OK\n    double b = max(3.14, 2.71); // OK\n    // float c = max(1.0f, 2.0f); // Error                

Blogs

Book Recommendations