Rozważ następujący program, który nie działa poprawnie:
main.cpp:
#include <iostream>
template <typename T>
T addOne(T x); // function template forward declaration
int main()
{
std::cout << addOne(1) << '\n';
std::cout << addOne(2.3) << '\n';
return 0;
}add.cpp:
template <typename T>
T addOne(T x) // function template definition
{
return x + 1;
}Jeśli addOne gdyby nie była to funkcja szablonowa, ten program działałby dobrze: W main.cpp kompilator byłby zadowolony z deklaracji forward addOne, a linker połączyłby wywołanie do addOne() w main.cpp do definicji funkcji w add.cpp.
Ale ponieważ addOne jest szablonem, ten program nie działa i pojawia się błąd linkera:
1>Project6.obj : error LNK2019: unresolved external symbol "int __cdecl addOne<int>(int)" (??$addOne@H@@YAHH@Z) referenced in function _main 1>Project6.obj : error LNK2019: unresolved external symbol "double __cdecl addOne<double>(double)" (??$addOne@N@@YANN@Z) referenced in function _main
W main.cpp, wywołujemy addOne<int> i addOne<double>. Jednakże, ponieważ kompilator nie widzi definicji szablonu funkcji addOne, nie może utworzyć instancji tych funkcji wewnątrz main.cpp. Widzi jednak deklarację forward dla addOne i zakłada, że te funkcje istnieją gdzie indziej i zostaną do nich dołączone później.
Gdy kompilator przejdzie do kompilacji add.cpp, zobaczy definicję szablonu funkcji addOne. Jednak w add.cpp nie ma zastosowań tego szablonu, więc kompilator nie utworzy niczego. Efektem końcowym jest to, że linker nie jest w stanie połączyć wywołań addOne<int> i addOne<double> w main.cpp z rzeczywistymi funkcjami, ponieważ te funkcje nigdy nie zostały utworzone.
Na marginesie…
Jeśli add.cpp gdyby utworzyły instancje tych funkcji, program skompilowałby się i połączył prawidłowo. Jednak takie rozwiązania są kruche i należy ich unikać: jeśli kod w add.cpp zostanie później zmieniony tak, że funkcje te nie będą już tworzone, program ponownie nie będzie mógł się połączyć. Lub jeśli main.cpp wywołasz inną wersję addOne (taki jak addOne<float>), która nie została utworzona w add.cpp, napotkamy ten sam problem.
Najbardziej konwencjonalnym sposobem rozwiązania tego problemu jest umieszczenie całego kodu szablonu w pliku nagłówkowym (.h) zamiast w pliku źródłowym (.cpp):
add.h:
#ifndef ADD_H
#define ADD_H
template <typename T>
T addOne(T x) // function template definition
{
return x + 1;
}
#endifmain.cpp:
#include "add.h" // import the function template definition
#include <iostream>
int main()
{
std::cout << addOne(1) << '\n';
std::cout << addOne(2.3) << '\n';
return 0;
}W ten sposób wszystkie pliki wymagające dostępu do szablonu będą mogły #include odpowiedni nagłówek, a definicja szablonu zostanie skopiowana przez preprocesor do pliku źródłowego. Kompilator będzie wtedy mógł utworzyć instancję dowolnych potrzebnych funkcji.
Możesz się zastanawiać, dlaczego nie powoduje to naruszenia reguły jednej definicji (ODR). ODR mówi, że typy, szablony, funkcje wbudowane i zmienne wbudowane mogą mieć identyczne definicje w różnych plikach. Zatem nie ma problemu, jeśli definicja szablonu zostanie skopiowana do wielu plików (o ile każda definicja jest identyczna).
Powiązana treść
Omówiliśmy ODR na lekcji 2.7 -- Deklaracje przesyłania dalej i definicje.
A co z samymi funkcjami utworzonymi w instancji? Jeśli instancja funkcji jest tworzona w wielu plikach, jak nie powoduje to naruszenia ODR? Odpowiedź jest taka, że funkcje utworzone niejawnie na podstawie szablonów są domyślnie wbudowane. Jak wiadomo, funkcje inline można definiować w wielu plikach, pod warunkiem, że definicja jest identyczna w każdym z nich.
Kluczowa informacja
Definicje szablonów są wyłączone z części reguły jednej definicji, która wymaga tylko jednej definicji na program, więc nie stanowi problemu #uwzględnienie tej samej definicji szablonu w wielu plikach źródłowych. A funkcje utworzone niejawnie na podstawie szablonów funkcji są domyślnie wbudowane, więc można je zdefiniować w wielu plikach, pod warunkiem, że każda definicja jest identyczna.
Same szablony nie są wbudowane, ponieważ koncepcja inline ma zastosowanie tylko do zmiennych i funkcji.
Oto kolejny przykład umieszczenia szablonu funkcji w pliku nagłówkowym, dzięki czemu można go uwzględnić w wielu źródłach files:
max.h:
#ifndef MAX_H
#define MAX_H
template <typename T>
T max(T x, T y)
{
return (x < y) ? y : x;
}
#endiffoo.cpp:
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>
void foo()
{
std::cout << max(3, 2) << '\n';
}main.cpp:
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>
void foo(); // forward declaration for function foo
int main()
{
std::cout << max(3, 5) << '\n';
foo();
return 0;
}W powyższym przykładzie zarówno main.cpp, jak i foo.cpp #include "max.h" więc kod w obu plikach może korzystać z max<T>(T, T) szablonu funkcji.
Najlepsza praktyka
Szablony potrzebne w wielu plikach należy zdefiniować w pliku nagłówkowym, a następnie #include tam, gdzie jest to potrzebne. Dzięki temu kompilator może zobaczyć pełną definicję szablonu i w razie potrzeby utworzyć instancję szablonu.

