Отсутствие конфликта имён при инстанцировании шаблона в разных модулях - почему?
Задам несколько необычный вопрос. В том смысле, что обычно ожидают, что код заработает, и спрашивают, почему он не работает. У меня же получилось наоборот: я ожидал, что будет ошибка, но всё слинковалось и работает.
Как известно, вся реализация шаблонного класса обретает плоть только тогда, когда он непосредственно инстанциируется. Т.е., когда в cpp-файле, допустим, объявляется переменная данного класса с заданными параметрами шаблона. Причём, все методы класса, а также его статические поля размещаются в том же объектном модуле, в который компилируется cpp-файл. Но отсюда следует и проблема: если cpp-файлов, в которых инстанциируется шаблон с одинаковыми параметрами, несколько, то каждый скомпилированный из этих файлов объектный модуль будет иметь по копии реализации класса. А следовательно, при линковке должна возникнуть ошибка - конфликт имён.
Проверим на практике. Вот h-файл с шаблоном:
/*
* File: common.h
*/
#ifndef common_h_included_
#define common_h_included_
//tested template
template <class T>
class CTestTemplate
{
static int s_num;
public:
static void print();
};
//tested template static method implementation
template <class T> void CTestTemplate<T>::print()
{
printf("sizeof(T) is %zu, s_num==%d\n", sizeof(T), s_num);
}
//tested template static field implementation
template <class T> int CTestTemplate<T>::s_num = sizeof(T)*2 + 1;
//functions prototype
void call_func(void (*func)());
void test_func2();
#endif //common_h_included_
Вот файл main.cpp:
/*
* FILE: main.cpp
*/
#include <stdio.h>
#include "common.h"
int main()
{
CTestTemplate<double> t; //instatntiating template in module main.o
//test 1
printf("*** test-1 ***\n");
call_func(CTestTemplate<double>::print);
//test 2
test_func2();
//test 3
printf("*** test-3 ***\n");
call_func(CTestTemplate<double>::print);
return 0;
}
Вот cpp-файл со вторым инстанциированием шаблона:
/*
* FILE: module2.cpp
*/
#include <stdio.h>
#include "common.h"
void test_func2()
{
CTestTemplate<double> t; //instatntiating template in module module2.o
//some testing code
printf("*** test-2 ***\n");
call_func(CTestTemplate<double>::print);
}
Вот файл с фанкцией call_func(), чтобы было интереснее (и чтобы компилятор гарантированно не сделал искомый метод inline):
/*
* FILE: call_func.cpp
*/
#include <stdio.h>
#include "common.h"
void call_func(void (*func)())
{
printf("Calling function...\n");
func();
}
Компилируем, собираем и... всё успешно собирается и работает! Ошибок у линкера не возникает! И тут бы задаться другим вопросом: а стоит ли по этому поводу беспокоится, писать на SO, ведь работает - значит всё хорошо. Но я так не могу, мне надо точно знать, почему нет конфликта на линкере? Ведь когда-то он был. Несколько лет назад из-за этого я не смог собрать проет, из-за чего тогда решил отказаться от шаблонов, за исключением полностью инлайновых. Неужели разработчики языка (вернее, компилятора) всё починили? Говорит ли что-нибудь по-этому поводу стандарт? Гарантируется ли, что конфликта линковки при работе с шаблонами не возникнет, если использовать любой другой современный компилятор, помимо GCC (да и насколько гарантируется это в самом GCC)?
P.S. Копии реализации присутствуют в обоих модулях: и в main.o, и в module2.o - это я проверил. Похоже, линкер понимает, что это копии, и в финальный экзешник помещает один экземпляр.