11.10 — Używanie szablonów funkcji w wielu plikach

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;
}

#endif

main.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;
}

#endif

foo.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.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
27 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze