Шаблоны имеют специальное свойство: их «реализация» может рассматриваться как часть их подписи.
Если компилятор в вашем примере увидит только строку
Terminallog &operator<<(const T &v);
Вы можете использовать оператор << абсолютно с любым !!И я буквально имею в виду яблоки и чернослив.Этот оператор был бы всемогущим! </p>
Однако вашему оператору РЕАЛИЗАЦИЯ требуется тип, который можно отправить в cout !!Что не верно для многих типов!Попробуйте передать свой класс Terminallog в cout.Это не может работать.И компилятор может только проверить на такое соответствие, если вы скажете ему, что вы хотите сделать с T.
Вторая часть: понять, что делает #include.C / C ++ (к сожалению) не знает реальной разницы между интерфейсом и реализацией, у него нет модульной системы.Заголовки, cpps - это всего лишь соглашение.Вы можете счастливо написать #include <something.cpp>
.Ничто не остановит вас.Оператор #include"inc/terminallog.hh"
просто сбрасывает все заголовки в текущий файл (и все вещи, которые там тоже включены #).Это немного удручающая причина, по которой мы используем include guard'a #ifndef TERMINALLOG_HH
, поскольку заголовки, такие как string или аналогичные, могут быть выгружены в наш файл сотни и более раз, и компилятор будет постоянно выдавать ошибки переопределения.На самом деле, если вы добавите `#include, это, скорее всего, удалит вашу ошибку, так как реализация Terminallog теперь также сбрасывается.Компилятор просматривает файлы реализации один за другим, извлекает включения и запускает их за один очень длинный проход.Затем он забывает более или менее все, что он только что сделал, и переходит к следующему файлу реализации, извлекает include и запускает.
Сначала компилирует Terminallog.cc, находит код для всего там, а такжеОстальная часть шаблона.Он не генерирует Terminallog :: operator << (string), хотя, потому что он там никогда не используется. </p>
Из частей 1 и 2 следует, что когда компилятор компилирует testMain.cpp, после выгрузки в терминалlog.hh,вот с чем он должен работать:
class Terminallog {
public:
Terminallog();
Terminallog(int);
virtual ~Terminallog();
template <class T>
Terminallog &operator<<(const T &v);
private:
};
int main() {
Terminallog clog(3);
clog << "bla";
clog << "bla";
return 0;
}
С точки зрения компиляторов все выглядит круто.Оператор << имеет параметр шаблона, поэтому компилятор записывает маркер вызова для <code>operator<<(T = const string), то же самое для constructor(int)
.Он не видит реализацию и не заботится.Вуаля, ваш всемогущий оператор.Он не знает, что у T должен быть оператор << с ostream в этой точке!Тем не менее, это законно, и он просто надеется, что вы дадите ему несколько подсказок о том, какой код должен быть сгенерирован, если не в этом файле, возможно, в следующем, может быть, в предыдущем.Компоновщик будет знать.Компилятор просто запоминает «если я найду шаблон тела функции, я сгенерирую для него код с T = string».Но, к сожалению, он никогда не получает такой возможности. </p>
Затем работает компоновщик, заменяет маркер call to constructor(int)
, ищет, найдет ли для него реализацию, находит его в Terminallog.cc.o.Затем он пытается найти Terminallog::operator<<(string)
, который никогда не был сгенерирован.Не здесь, не в файле Terminallog.cc.o, нигде и безуспешно.
Компилятор генерирует код для функции шаблона только тогда, когда он также видит тело функции.Вы можете попробовать просто выполнить Terminallog clog (3);сабо << "бла";в конструкторе Terminallog в Terminallog.cc.Когда компилятор увидит это, он сгенерирует Terminallog :: operator << (string), и компоновщик найдет его в скомпилированном объектном файле Terminallog.cc.o. </p>
Это должно сказать вам, что у вас естьпредоставить тело функции в заголовке, чтобы его можно было выгружать в каждый файл .cc, который его использует, и компилятор может генерировать то, что ему нужно, на лету.В противном случае вы должны угадать, что может быть передано для T, что даже более неприятно, чем слегка раздутые заголовочные файлы.
(В том, что я здесь написал, есть некоторые упрощения и неточности, но суть этогодолжен быть здоровым.)