Introdução: A dor familiar de todo desenvolvedor C++
\n\nVocê já escreveu uma classe template organizada, separou a declaração no arquivo .h e a implementação no .cpp, compilou o projeto e recebeu do linker algo como "undefined reference to ..."? Se sim — você não está sozinho. Esta pergunta no Stack Overflow acumulou milhares de visualizações e centenas de comentários. O tópico "why can templates only be implemented in the header file" é uma verdadeira pedra no sapato para iniciantes e até mesmo para programadores experientes que estão migrando para C++ de outras linguagens.
Neste artigo, vamos entender de uma vez por todas por que os templates C++ exigem essa abordagem, como funciona o modelo de compilação e o que fazer se você realmente quiser esconder a implementação no .cpp. Vamos lá!
1. Como o compilador enxerga um template? Modelo de compilação C++
\n\nPara entender o problema, precisamos lembrar como o C++ compila código comum. O processo consiste em duas etapas:
\n\n- \n
- Compilação — cada arquivo
.cpp(unidade de tradução) é compilado independentemente. O compilador vê apenas o que está incluído neste arquivo através de#include. \n - Linkagem — os arquivos objeto (
.oou.obj) são reunidos, e as definições de funções, variáveis e classes são procuradas. \n
Agora imagine que você tem um template de função max:
// max.h\ntemplate <typename T>\nT max(T a, T b); // Apenas declaração!\n\n// max.cpp\n#include "max.h"\ntemplate <typename T>\nT max(T a, T b) {\n return (a > b) ? a : b;\n}\n\n// main.cpp\n#include "max.h"\nint main() {\n int result = max(3, 5); // O compilador vê apenas a declaração!\n return 0;\n}\n\nAo compilar main.cpp, o compilador encontra a chamada max(3,5). Ele sabe que max é um template, mas não vê sua implementação. O compilador não consegue instanciar o template com o tipo int, porque a definição está em outro arquivo (max.cpp), que é compilado separadamente. Como resultado, ele gera uma chamada com referência externa, e o linker não encontra uma função instanciada adequada — daí o erro.
Essa é a raiz do problema: os templates C++ precisam ser instanciados pelo compilador durante a compilação, e para isso o compilador precisa ver a definição completa em cada unidade de tradução onde o template é usado.
\n\n2. Por que isso não funciona como com funções comuns?
\n\nAo contrário das funções comuns, um template não é um código pronto. É uma "instrução" para o compilador: "quando você vir o uso com o tipo int, gere uma função para int". Se a implementação estiver escondida no .cpp, o compilador em main.cpp não recebe essa instrução e não consegue criar o código necessário.
Imagine que você tem uma receita (template), mas a receita está em um cofre (em outro arquivo .cpp). O cozinheiro (compilador) vê apenas o nome do prato, mas não sabe como prepará-lo. O prato não sai. Para uma função comum, a receita já está pronta — o cozinheiro apenas pega o prato pronto da geladeira (do arquivo objeto).
3. Solução clássica: tudo no arquivo de cabeçalho
\n\nA maneira mais simples e comum é colocar a implementação diretamente no arquivo de cabeçalho. Esta é uma técnica padrão, conhecida como bibliotecas "header-only". Por exemplo:
\n\n// max.h\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\nAgora, qualquer arquivo que inclua max.h recebe a definição completa do template. O compilador pode instanciá-lo para qualquer tipo necessário. É assim que funcionam bibliotecas populares como Boost e grande parte da STL. Desvant