Вам не нужно создавать какие-либо объекты, инстанцирующие MyTemplate1<T>
в модуле компиляции, чтобы увидеть объекты typeinfo, описывающие классы реализации
этого шаблона в глобальной таблице символов объектного файла. Вам нужно только
сослаться на typeid
такого класса: -
$ cat main.cpp
#include <typeinfo>
template<class T>
class MyTemplate1
{
public:
T a;
MyTemplate1(T other){
a = other;
}
};
int main(void)
{
return (typeid(MyTemplate1<int>) == typeid(MyTemplate1<float>));
}
$ clang++ -Wall -c main.cpp
$ readelf -s -W main.o | grep MyTemplate1
5: 0000000000000000 16 OBJECT WEAK DEFAULT 15 _ZTI11MyTemplate1IfE
6: 0000000000000000 16 OBJECT WEAK DEFAULT 10 _ZTI11MyTemplate1IiE
7: 0000000000000000 17 OBJECT WEAK DEFAULT 13 _ZTS11MyTemplate1IfE
8: 0000000000000000 17 OBJECT WEAK DEFAULT 8 _ZTS11MyTemplate1IiE
$ c++filt _ZTI11MyTemplate1IfE
typeinfo for MyTemplate1<float>
$ c++filt _ZTI11MyTemplate1IiE
typeinfo for MyTemplate1<int>
$ c++filt _ZTS11MyTemplate1IfE
typeinfo name for MyTemplate1<float>
$ c++filt _ZTS11MyTemplate1IiE
typeinfo name for MyTemplate1<int>
Эти typeinfo
объекты существуют, потому что, как прокомментировал @Peter, стандарт C ++
требует, чтобы typeid
ссылался на объект статической длительности хранения
Что именно происходит под капотом?
Вы можете задаться вопросом: почему компилятор делает эти typeinfo
символы объекта слабыми , а не просто глобальными?
Почему он определяет их в разных разделах объектного файла? (разделы 10 и 15 моего объектного файла,
твои разделы 2894 и 2899).
И если мы проверим, что еще находится в этих разделах:
$ readelf -s main.o | egrep '(10 |15 )'
5: 0000000000000000 16 OBJECT WEAK DEFAULT 15 _ZTI11MyTemplate1IfE
6: 0000000000000000 16 OBJECT WEAK DEFAULT 10 _ZTI11MyTemplate1IiE
мы видим, что каждый объект является единственным в своем разделе. Почему так?
В моем main.o
эти разделы 10 и 15:
$ readelf -t main.o | egrep '(\[10\]|\[15\])'
[10] .rodata._ZTI11MyTemplate1IiE
[15] .rodata._ZTI11MyTemplate1IfE
Каждый из них - только для чтения раздел данных в смысле:
__attribute__((section(.rodata._ZTI11MyTemplate1IiE)))
__attribute__((section(.rodata._ZTI11MyTemplate1IfE)))
, который содержит ничего, кроме определения объекта, после которого он
имя.
Компилятор предоставляет каждому из объектов раздел данных для всех себе
по той же причине, по которой он делает символы WEAK
.
Ссылки на typeid(MyTemplate1<X>)
для произвольного типа X
могут быть сделаны в
несколько единиц перевода в одной и той же связи, что #include
определение
MyTemplate1
. Чтобы предотвратить сбой связи в таких случаях с ошибкой множественное определение ,
компилятор делает символы слабыми.
Компоновщик допускает множественные определения символа слабый , разрешая
все ссылки просто на первое определение, которое представляет собой и игнорирует
остальные. Посвящая уникальный раздел данных (или функциональный раздел, в зависимости от обстоятельств)
определение каждого слабого шаблона-экземпляра символа компилятор дает компоновщику свободу
отбрасывать любые лишние секции данных или функций, которые определяют один и тот же слабый символ без
риск побочного ущерба для программы. См:
$ cat MyTemplate1.hpp
#pragma once
template<class T>
class MyTemplate1
{
public:
T a;
MyTemplate1(T other){
a = other;
}
};
$ cat foo.cpp
#include "MyTemplate1.hpp"
#include <typeinfo>
int foo()
{
return typeid(MyTemplate1<int>) == typeid(MyTemplate1<float>);
}
$ cat bar.cpp
#include "MyTemplate1.hpp"
#include <typeinfo>
int bar()
{
return typeid(MyTemplate1<int>) != typeid(MyTemplate1<float>);
}
$ cat prog.cpp
extern int foo();
extern int bar();
int main()
{
return foo() && bar();
}
Если мы скомпилируем:
$ clang++ -Wall -c prog.cpp foo.cpp bar.cpp
и ссылка (с некоторыми диагностиками), как это:
$ clang++ -o prog prog.o bar.o foo.o \
-Wl,-trace-symbol=_ZTI11MyTemplate1IfE \
-Wl,-trace-symbol=_ZTI11MyTemplate1IiE \
-Wl,-Map=mapfile
/usr/bin/ld: bar.o: definition of _ZTI11MyTemplate1IfE
/usr/bin/ld: bar.o: definition of _ZTI11MyTemplate1IiE
/usr/bin/ld: foo.o: reference to _ZTI11MyTemplate1IfE
/usr/bin/ld: foo.o: reference to _ZTI11MyTemplate1IiE
ввод bar.o
перед foo.o
, тогда компоновщик выбирает определения
_ZTI11MyTemplate1I(f|i)E
из bar.o
и игнорирует определения в foo.o
,
разрешение ссылок в foo.o
на определения в bar.o
. И файл карты показывает:
mapfile (1)
...
Discarded input sections
...
.rodata._ZTI11MyTemplate1IiE
0x0000000000000000 0x10 foo.o
...
.rodata._ZTI11MyTemplate1IfE
0x0000000000000000 0x10 foo.o
...
что определения в foo.o
были отброшены. Если мы связываем с
порядок bar.o
и foo.o
в обратном порядке:
$ clang++ -o prog prog.o foo.o bar.o \
-Wl,-trace-symbol=_ZTI11MyTemplate1IfE \
-Wl,-trace-symbol=_ZTI11MyTemplate1IiE \
-Wl,-Map=mapfile
/usr/bin/ld: foo.o: definition of _ZTI11MyTemplate1IfE
/usr/bin/ld: foo.o: definition of _ZTI11MyTemplate1IiE
/usr/bin/ld: bar.o: reference to _ZTI11MyTemplate1IfE
/usr/bin/ld: bar.o: reference to _ZTI11MyTemplate1IiE
тогда мы получим противоположные результаты. Определения из foo.o
связаны между собой
и:
mapfile (2)
...
Discarded input sections
...
.rodata._ZTI11MyTemplate1IiE
0x0000000000000000 0x10 bar.o
...
.rodata._ZTI11MyTemplate1IfE
0x0000000000000000 0x10 bar.o
...
те, что в bar.o
выброшены. Это первым пришел-первым обслужен
принцип компоновщика хорош, потому что - и только , потому что - определение template<class T> MyTemplate1
, найденное компилятором
в единице перевода foo.cpp
было идентично тому, что было найдено в bar.cpp
, a
условие, что Стандарт C ++ требует , в Правило единого определения ,
но который компилятор C ++ ничего не может сделать для принудительного применения .
Вы можете сделать, по существу, те же самые наблюдения относительно символов создания шаблонов в целом, и то, что вы видите с помощью clang ++, по существу совпадает с тем, что вы увидите с g ++.