См. следующий код и очистите мои сомнения.
Поскольку ABC является шаблоном, почему она не показывает ошибку, когда мы помещаем определение функции членства класса ABC в test.cpp?
Если я поместил код test.cpp в test.h и remve 2, то он хорошо работает. Почему?
.
// test.h
template <typename T>
class ABC {
public:
void foo( T& );
void bar( T& );
};
// test.cpp
template <typename T>
void ABC<T>::foo( T& ) {} // definition
template <typename T>
void ABC<T>::bar( T& ) {} // definition
template void ABC<char>::foo( char & ); // 1
template class ABC<char>; // 2
// main.cpp
#include "test.h"
int main() {
ABC<char> a;
a.foo(); // valid with 1 or 2
a.bar(); // link error if only 1, valid with 2
}
В обоих случаях вы выполняете явное создание экземпляра. Во втором случае создается только ABC
, а в первом случае также создается ABC
.
Другой подобный пример может прояснить последствия:
// test.h
template <typename T>
class ABC {
public:
void foo( T& );
void bar( T& );
};
// test.cpp
template <typename T>
void ABC<T>::foo( T& ) {} // definition
template <typename T>
void ABC<T>::bar( T& ) {} // definition
template void ABC<char>::foo( char & ); // 1
template class ABC<char>; // 2
// main.cpp
#include "test.h"
int main() {
ABC<char> a;
a.foo(); // valid with 1 or 2
a.bar(); // link error if only 1, valid with 2
}
В примере в main
компилятор не может видеть определения foo
или bar
, поэтому он не может создавать экземпляры методов. Компилятор при обработке main.cpp с радостью примет код в основном, поскольку вы говорите ему, что ABC
является шаблоном и что он имеет эти две функции, и будет предполагать, что они будут определены в некоторых другая единица перевода.
В блоке трансляции, который содержит test.cpp, компилятор видит оба определения методов, и оба экземпляра (метод / класс) могут быть полностью обработаны. Если присутствует только экземпляр метода ([1]), компилятор сгенерирует только этот метод и оставит bar
неопределенным.Таким образом, любой код, который включает test.h, ссылается на скомпилированный test.cpp и использует только метод foo
, будет компилироваться и компоноваться, но использование bar
не сможет связать из-за того, что он неопределенный.
Явное создание экземпляра шаблона класса генерирует символы для всех методов-членов, и в этом случае любая единица трансляции, которая включает test.h и ссылки на скомпилированный объектный файл test.cpp, будет компилироваться и связываться.
(Это отредактированная версия моего первоначального ответа, подсказанная наблюдением Дэвида Родригеса.)
# 1
представляет собой экземпляр шаблон класса, и как часть этого создает экземпляры всех его методов.
# 2
создает экземпляр одного метода-члена класса. В рамках этого он должен создать экземпляр шаблона класса, но не всех его других методов.
Разницу можно увидеть, если ввести в bar () зависящую от типа ошибку (например, такой оператор, как void * x = b;
). Вы получите ошибку компилятора с # 1
, но не с # 2
. Также обратите внимание, что компилятор (по крайней мере, gcc) не будет компилировать # 1
, за которым следует # 2
, но будет компилировать любой из них без другого, или если # 2
, за ним последовал # 1
.