Тема: Чому не можна розбивати шаблони на h і cpp файли ? [Стаття застаріла]
Зазвичай, коли ми пишемо код на С++, ми розділюємо його на h і cpp файли, в h файли пишемо оголошення функцій/класів, а в cpp файли - їх реалізацію.
І все прекрасно компілюється і запускається.
Але коли ми робимо так з шаблонами (template<...>):
оголошення
// functions.h #pragma once template<typename T> void function(T value);
реалізація
// functions.cpp #include "functions.h" template<typename T> void function(T value) { // Some code }
використання
// main.cpp #include "functions.h" void main() { function<int>(1); }
то середовище розробки видає помилки компоновщика :
error LNK2019: unresolved external symbol "void __cdecl function<int>(int)" (??$function@H@@YAXH@Z) in function _main
error LNK1120: 1 unresolved externalserror LNK2019: ссылка на неразрешенный внешний символ "void __cdecl function<int>(int)" (??$function@H@@YAXH@Z) в функции _main
error LNK1120: 1 неразрешенных внешних элементов
Не надто інформативні повідомлення, правда ?
Для того щоб розібратися, в чому проблема, потрібно знати що таке шаблон (template) і як працюють компілятор (compiler) і компоновщик (linker).
Шаблони:
Шаблони функцій - це частини коду, які починаються з "template<typename T>" (новий стиль) або "template<class T>" (старий стиль), далі виглядають як звичайні функції (за винятком типу T) і описують однакову поведінку для різних типів-параметрів T. Замість T можуть бути будь-які інші імена. При цьому вони не є реалізаціями функцій, лише заготовками. Це одна з причин, чому не варто їх класти в cpp файли. Також вони не є повноцінними оголошеннями.
Реалізації (і/або оголошення) функцій формуються тоді, коли щось їх викликає, передаючи відомі типи в якості типу-параметра.
Шаблони класів і структур точно такі самі, тільки у відношенні класів і структур, а не функцій.
Висновок: Шаблони не можуть оголошувати і реалізувати щось, можуть тільки описувати оголошення і реалізації. Ці описи оголошень і реалізацій перетворюються в самі оголошення і реалізації, коли їх (шаблони, описи) викликають, передаючи відомі типи як параметри шаблону.
Компіляція і компонування:
Спочатку компілятор бере всі файли, які підлягають компіляції (зазвичай: h, c і cpp файли), перевіряє їх на наявність помилок, і якщо помилок немає, формує відповідні obj файли і передає їх компоновщику, або повертає їх в середовище розробки і воно передає їх компоновщику.
Тобто, якщо в нас є файли "functions.h", "functions.cpp" і "main.cpp", то будемо мати "functions.obj" і "main.obj".
Далі компоновщик перевіряє obj файли на наявність помилок, і якщо їх немає, формує dll або exe файл.
Якщо хоча б в одному obj файлі є виклик функцій/класів, які не реалізовані в жодному з obj файлів, то це помилка, виконуваний файл не зформується і компоновщик поверне помилки.
Саме такого типу помилка сталася в наведеному прикладі.
Але чому ?
По порядку:
Синтаксичних помилок немає, тому компілятор видав нам 2 файли "functions.obj" і "main.obj".
Компоновщик заглянув в "main.obj" і зустрів там виклик функції "function<int>(1);", яка не реалізована в самому "main.obj" (хоч і оголошена в ньому), тому він шукає реалізацію в інших файлах, в нашому випадку тільки в "functions.obj". І "functions.obj" майже порожній, в ньому також немає реалізації цієї функції.
https://(агресор віджав цей домен)/images/2016/03/19/677a9467d48abd2119c90934d4bfead1.png
Шаблони не можуть оголошувати і реалізувати ...
Але "main.obj" містить оголошення цієї функції. Як це розуміти ?
Описи оголошень і реалізацій перетворюються в самі оголошення і реалізації, коли їх викликають ...
Тобто, коли компілятор побачив "function<int>(1);" в "main.cpp", він підтягнув оголошення з файлу "functions.h", бо в "main.cpp" є #include"functions.h", перетворив його на:
void function(int value);
і вставив в "main.obj", а реалізацію не вставив, бо "functions.cpp" не був підключений в "main.cpp".
От і маємо один із способів вирішити проблему, підключити "functions.cpp" в "main.cpp":
// main.cpp
#include "functions.h"
#include "functions.cpp"
void main() {
function<int>(1);
}
Але це трохи не гарно.
Краще вирізати шаблонний код з "functions.cpp" і вставити в "functions.h". Де саме вставляти код ? Найкраще в кінець файлу (за умови, що код не спричиняє інших помилок):
// functions.h
#pragma once
template<typename T>
void function(T value);
template<typename T>
void function(T value) {
// Some code
}
// main.cpp
#include "functions.h"
void main() {
function<int>(1);
}
А файл "functions.cpp" можна видалити, якщо в ньому були тільки шаблони.