Как явные экземпляры шаблонов влияют на то, что может найти компоновщик?

См. следующий код и, пожалуйста, очистите мои сомнения.

  • Поскольку ABC является шаблоном, почему он не показывает ошибку, когда мы помещаем определение функции члена класса ABC в test.cpp?

  • Если я поставлю код test.cpp в test.h и remes 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 
} 

Ответ 1

В обоих случаях вы выполняете явное инстанцирование. Во втором случае создается только ABC<char>::foo, в то время как в первом случае также создается экземпляр ABC<char>::bar.

В другом подобном примере можно прояснить последствия:

// 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 undefined. Таким образом, любой код, который включает в себя test.h, ссылки на скомпилированный test.cpp и использует только метод foo, будет компилировать и связывать, но использование bar не будет связываться из-за того, что оно undefined.

Явное копирование шаблона класса генерирует символы для всех методов-членов, и в этом случае любая единица перевода, включающая test.h и ссылки на скомпилированный файл объекта test.cpp, будет компилироваться и связываться.

Ответ 2

(Это отредактированная версия моего первоначального ответа, вызванная наблюдением Дэвида Родригеса.)

#1 создает экземпляр шаблона класса и как часть этого экземпляра создает все его методы.

#2 создает экземпляр метода одного члена класса. Как часть этого, он должен создать экземпляр шаблона класса, но не все его другие методы.

Разницу можно увидеть, если вы вводите зависящую от типа ошибку в bar() (например, оператор вроде void *x = b;). Вы получите ошибку компилятора с #1, но не с #2. Также обратите внимание, что компилятор (по крайней мере, gcc) не будет компилировать #1, а затем #2, но будет компилировать любой из них без другого, или если за #2 следует #1.

Ответ 3

Я думаю, вы хотели иметь {} вместо; в # 1.