Внутрянка инстанциаций шаблонов
Хочу в этом небольшом посте в явном виде продемонстрировать вам, что значит определение шаблона и его явная/неявная инстанциация. Сразу говорю, что буду использовать gcc в качестве компилятора.
Возьмем тот же пример с кораблем и оставим от него только хэдэр и сорец:
Сейчас в единице трансляции, соответствующей ship.cpp, есть только объявление и определение шаблона, больше ничего. Это значит, что никакого кода для этого юнита генерироваться не будет.
Проверим это с помощью утилитки nm, которая показывает символы бинарника. Скомпилируем ship.cpp в объектный файл и посмотрим, какие там символы есть внутри:
Как говорят математики: ЧТД и точка!
Теперь проверим неявную инстанциацию. Добавим в ship.cpp функцию:
Посмотрим теперь на символы этого юнита. Помимо всего прочего побочного непотребства, получим следующее:
Теперь мы имеем скомпилированную функцию foo и метод TurnShip класса Ship параметризованного строкой.
Для явной инстанциации уберем из ship.cpp функцию foo и добавим строчку:
и посмотрим на символы:
Как и ожидалось в принципе.
Вот поэтому-то при использовании подхода разделения шаблона на объявление в хэдэре и определение в сорце без явной инстанциации будет ошибка линковки. В единице трансляции, соответствующей определению шаблона, не будет никакого кода. Единицы трансляции на этапе компиляции не обмениваются информацией, поэтому компилятор не сможет инстанцировать шаблон на этом этапе. А на этапе линковки уже поздно смотреть на определение шаблона, потому что его просто не будет. Текст уберется, а кода никакого сгенерировано не будет. Как-то так.
Я не просто так вам это все рассказываю. Это нужно для понимания дальнейших постов.
Don't rely on words, check them. Stay cool.
#cppcore #template #compiler
Хочу в этом небольшом посте в явном виде продемонстрировать вам, что значит определение шаблона и его явная/неявная инстанциация. Сразу говорю, что буду использовать gcc в качестве компилятора.
Возьмем тот же пример с кораблем и оставим от него только хэдэр и сорец:
// ship.hpp
template<typename T>
struct Ship
{
// contain some fields
void TurnShip(T command);
};
// ship.cpp
#include "ship.hpp"
#include <string>
template <class T>
void Ship<T>::TurnShip(T command) {/* do stuff using command */}
Сейчас в единице трансляции, соответствующей ship.cpp, есть только объявление и определение шаблона, больше ничего. Это значит, что никакого кода для этого юнита генерироваться не будет.
Проверим это с помощью утилитки nm, которая показывает символы бинарника. Скомпилируем ship.cpp в объектный файл и посмотрим, какие там символы есть внутри:
nm ship.o
//Output
ship.o: no symbols
Как говорят математики: ЧТД и точка!
Теперь проверим неявную инстанциацию. Добавим в ship.cpp функцию:
void foo() {
Ship<std::string> ship{};
ship.TurnShip(std::string{"Turn upside down"});
}
Посмотрим теперь на символы этого юнита. Помимо всего прочего побочного непотребства, получим следующее:
nm ship.o
//Output
0000000000000000 T __Z3foov
0000000000000060 T __ZN4ShipINSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEE8TurnShipES6_
Теперь мы имеем скомпилированную функцию foo и метод TurnShip класса Ship параметризованного строкой.
Для явной инстанциации уберем из ship.cpp функцию foo и добавим строчку:
template struct Ship<std::string>;
и посмотрим на символы:
nm ship.o
//Output
0000000000000000 T __ZN4ShipINSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEEE8TurnShipES6_
Как и ожидалось в принципе.
Вот поэтому-то при использовании подхода разделения шаблона на объявление в хэдэре и определение в сорце без явной инстанциации будет ошибка линковки. В единице трансляции, соответствующей определению шаблона, не будет никакого кода. Единицы трансляции на этапе компиляции не обмениваются информацией, поэтому компилятор не сможет инстанцировать шаблон на этом этапе. А на этапе линковки уже поздно смотреть на определение шаблона, потому что его просто не будет. Текст уберется, а кода никакого сгенерировано не будет. Как-то так.
Я не просто так вам это все рассказываю. Это нужно для понимания дальнейших постов.
Don't rely on words, check them. Stay cool.
#cppcore #template #compiler
Extern template
Дисклеймер: здесь и далее в этой серии статей я буду называть "специализацией" шаблона его инстанциацию с конкретным шаблонным параметром.
Помните, как мы явно инстанцировали шаблон в файле реализации? Раз мы может сказать компилятору, чтобы он инстанцировал нужную специализацию в нужном нам файле, то очень удобно потом обращаться за кодом этой специализации конкретно в эту единицу трансляции. Потому что, как мы уже знаем, код одной и той же специализации может генерироваться во всех единицах трансляции, не вызывая при этом конфликтов и нарушения ODR(вектор интов инстанцируется много где, но никогда эти специализации не конфликтуют при линковке). Тут аналогия с inline сущностями.
В общем, хочется иметь один образцово-показательный код в одной TU и обращаться за нужными символами туда.
И такой инструмент есть!
Называется extern template declaration. Этот пост, а также несколько предыдущих и следующих, посвящается нашему олду @PyXiion и его просьбе разобрать этот вопрос.
Идея очень похожа на extern объявления других сущностей. С его помощью обеспечивается внешнее связывание и оно помогает компилятору понять, что такая сущность в программе есть, просто она описана в другой единице трансляции. Это значит, что на этапе линковки компилятор может посмотреть в эту самую другую TU и найти там все, что нужно для генерации полноценного кода.
Синтаксис следующий:
extern template class-key template-name <argument-list>;
Все то же самое, что и при явной инстанциации, только спереди добавляем extern. Фича из С++11 кстати и скорее всего средний разработчик даже не представляет о ее существовании.
Что происходит, когда мы добавляем эту строчку после определения шаблона? Неявная конкретизация этой специализации запрещается. То есть даже если компилятор видит полное определение шаблона и вы создаете объект конкретной специализации, то код для нее генерироваться не будет. Вместо этого компилятор будет ждать, что он найдет нужные символы и код для них в другой единице трансляции.
В основном, эта вещь решает проблему дублирования кода специализации во всех TU, где она используется. Очевидно, что если все TU будут обращаться к одной единственной за всем нужным, а не будут генерировать все сами, то будет всего один оригинал кода и соотвественно размер бинаря уменьшится. Как и время компиляции собственно.
Как это работает и какие еще проблемы решает эта фича, мы рассмотрим в следующих частях, все в один пост не влезет, много тонких моментов, требующих акцентов.
Learn new things. Stay cool.
#cppcore #cpp11 #template #compiler
Дисклеймер: здесь и далее в этой серии статей я буду называть "специализацией" шаблона его инстанциацию с конкретным шаблонным параметром.
Помните, как мы явно инстанцировали шаблон в файле реализации? Раз мы может сказать компилятору, чтобы он инстанцировал нужную специализацию в нужном нам файле, то очень удобно потом обращаться за кодом этой специализации конкретно в эту единицу трансляции. Потому что, как мы уже знаем, код одной и той же специализации может генерироваться во всех единицах трансляции, не вызывая при этом конфликтов и нарушения ODR(вектор интов инстанцируется много где, но никогда эти специализации не конфликтуют при линковке). Тут аналогия с inline сущностями.
В общем, хочется иметь один образцово-показательный код в одной TU и обращаться за нужными символами туда.
И такой инструмент есть!
Называется extern template declaration. Этот пост, а также несколько предыдущих и следующих, посвящается нашему олду @PyXiion и его просьбе разобрать этот вопрос.
Идея очень похожа на extern объявления других сущностей. С его помощью обеспечивается внешнее связывание и оно помогает компилятору понять, что такая сущность в программе есть, просто она описана в другой единице трансляции. Это значит, что на этапе линковки компилятор может посмотреть в эту самую другую TU и найти там все, что нужно для генерации полноценного кода.
Синтаксис следующий:
extern template class-key template-name <argument-list>;
Все то же самое, что и при явной инстанциации, только спереди добавляем extern. Фича из С++11 кстати и скорее всего средний разработчик даже не представляет о ее существовании.
Что происходит, когда мы добавляем эту строчку после определения шаблона? Неявная конкретизация этой специализации запрещается. То есть даже если компилятор видит полное определение шаблона и вы создаете объект конкретной специализации, то код для нее генерироваться не будет. Вместо этого компилятор будет ждать, что он найдет нужные символы и код для них в другой единице трансляции.
В основном, эта вещь решает проблему дублирования кода специализации во всех TU, где она используется. Очевидно, что если все TU будут обращаться к одной единственной за всем нужным, а не будут генерировать все сами, то будет всего один оригинал кода и соотвественно размер бинаря уменьшится. Как и время компиляции собственно.
Как это работает и какие еще проблемы решает эта фича, мы рассмотрим в следующих частях, все в один пост не влезет, много тонких моментов, требующих акцентов.
Learn new things. Stay cool.
#cppcore #cpp11 #template #compiler
Когда НЕ стоит использовать extern template
Если вы будете гуглить инфу по этой теме, то непременно нарветесь на неправильное понимание принципов работы фичи. Стопроцентов вы наткнетесь на такое объяснение:
extern template в связке с явным инстанцированием шаблона помогает предотвратить дублирование кода в TU и уменьшить время компиляции. Выглядит это так:
Типа вот мы в ship.cpp добавили явную конкретизацию шаблона, а в мэйне объявили, что возьмем информацию о специализации в другом месте.
Но дело в том, что в этом случае extern template - лишний! В нем нет никакого смысла и вот почему.
Если мы уберем extern template из файла main, то ничего не изменится. Так как в этой единице трансляции и так никогда бы не была конкретизирована специализация Ship<int>. Потому что компилятору на момент компиляции файла main.cpp видно только объявление шаблона из файла ship.hpp и у него недостаточно информации для инстанцирования. И только при линковке линковщик найдет все символы в единице трансляции, соответствующей ship.cpp, и сгенерирует рабочую программу.
Так что запомните: если вы используете явную инстанциацию после определения шаблона в цппшнике и подключаете хэдэр с его объявлением, то вам НЕ НУЖНО использовать extern template.
Это кстати отличная защита от тех самых проблем при работе с шаблонами. Так что выносить определенения шаблонов в цппшники и делать в них явную инстанциацию - полезная вещь.
Также без подключения хэдэра эта вещь вообще не работает, в отличии например от глобальных переменных. Все-таки контекст extern здесь не совсем совпадает.
А вот когда это нужно использовать. Только тогда, когда у вас есть несколько единиц трансляции, где компилятор сам неявно может инстанцировать одинаковые специализации. Например, когда вы полностью определяете шаблон в хэдэре и везде его распространяете таким образом. Тогда получается, что без использования extern template в каждой из этих единиц трансляций, подключивших хэдэр с шаблоном и использующих одинаковую специализацию, эта специализация будет инстанцирована. Это значит, что код для нее будет присутствовать во всех объектниках. Это приводит к его дублированию и увеличению времени компиляции.
Теперь мы во всех TU, кроме одной, используем extern template и в этой оставшейся делаем явную специализацию. Получается, что для всех, кроме одной, TU компилятору будет запрещено самостоятельно инстанцировать эту специализацию. И все они будет обращаться в тот единственный объектник, в котором есть код для специализации. Именно за счет этого и не происходит раздувания итогового бинарника. Все просто полагаются на одну копию.
Rely on original information. Stay cool.
#cpp11 #cppcore #template #compiler
Если вы будете гуглить инфу по этой теме, то непременно нарветесь на неправильное понимание принципов работы фичи. Стопроцентов вы наткнетесь на такое объяснение:
extern template в связке с явным инстанцированием шаблона помогает предотвратить дублирование кода в TU и уменьшить время компиляции. Выглядит это так:
// ship.hpp
#pragma once
template<typename T>
struct Ship
{
void TurnShip(T command);
};
// ship.cpp
#include "ship.hpp"
template <class T>
void Ship<T>::TurnShip(T command) {}
template struct Ship<int>; // explicit instantiation definition
// main.cpp
#include "ship.hpp"
extern template struct Ship<int>; // explicit instantiation declaration
int main() {
Ship<int> ship;
ship.TurnShip(5);
}
Типа вот мы в ship.cpp добавили явную конкретизацию шаблона, а в мэйне объявили, что возьмем информацию о специализации в другом месте.
Но дело в том, что в этом случае extern template - лишний! В нем нет никакого смысла и вот почему.
Если мы уберем extern template из файла main, то ничего не изменится. Так как в этой единице трансляции и так никогда бы не была конкретизирована специализация Ship<int>. Потому что компилятору на момент компиляции файла main.cpp видно только объявление шаблона из файла ship.hpp и у него недостаточно информации для инстанцирования. И только при линковке линковщик найдет все символы в единице трансляции, соответствующей ship.cpp, и сгенерирует рабочую программу.
Так что запомните: если вы используете явную инстанциацию после определения шаблона в цппшнике и подключаете хэдэр с его объявлением, то вам НЕ НУЖНО использовать extern template.
Это кстати отличная защита от тех самых проблем при работе с шаблонами. Так что выносить определенения шаблонов в цппшники и делать в них явную инстанциацию - полезная вещь.
Также без подключения хэдэра эта вещь вообще не работает, в отличии например от глобальных переменных. Все-таки контекст extern здесь не совсем совпадает.
А вот когда это нужно использовать. Только тогда, когда у вас есть несколько единиц трансляции, где компилятор сам неявно может инстанцировать одинаковые специализации. Например, когда вы полностью определяете шаблон в хэдэре и везде его распространяете таким образом. Тогда получается, что без использования extern template в каждой из этих единиц трансляций, подключивших хэдэр с шаблоном и использующих одинаковую специализацию, эта специализация будет инстанцирована. Это значит, что код для нее будет присутствовать во всех объектниках. Это приводит к его дублированию и увеличению времени компиляции.
Теперь мы во всех TU, кроме одной, используем extern template и в этой оставшейся делаем явную специализацию. Получается, что для всех, кроме одной, TU компилятору будет запрещено самостоятельно инстанцировать эту специализацию. И все они будет обращаться в тот единственный объектник, в котором есть код для специализации. Именно за счет этого и не происходит раздувания итогового бинарника. Все просто полагаются на одну копию.
Rely on original information. Stay cool.
#cpp11 #cppcore #template #compiler
Линковочная природа шаблонов
Когда мы говорим про шаблоны и их линковку, нам важно видеть все детали общей картины. Иначе полное понимание так и не придет. Поэтому сегодня немного больше приоткроем линковочные тайны темплейтов.
Думаю, что не будет грубым обобщением сказать, что каждый из нас пользовался шаблонным классом std::vector. И это хорошо, все имеют опыт с ним и всем будет проще понимать, о чем сейчас пойдет речь.
Представим, что мы разрабатываем какое-то приложение или отдельный сервис. Этот сервис состоит из отдельных кодовых модулей, которые отдельно компилируются и линкуются вместе для получения готового бинарника. Так вот очень легко допустить, что во многих модулях одного и того же сервиса используется вектор интов. std::vector<int>. Обычно мы просто инклюдим в эти модули хэдэр вектора и после используем его. Таким образом происходит неявная инстанциация. То есть компилятор на этапе компиляции модулей сам инстанциацирует интовую специализацию вектора в каждой единице трансляции и использует ее в коде текущего юнита.
Однако, погодите-ка. То есть у нас в нескольких единицах трансляции есть одна и та же скомпилированная сущность и при линковке это не вызывает никаких проблем. Как так?
Вот, что стандарт говорит по поводу этого:
Стандарт разрешает шаблонным сущностям иметь больше, чем одно определение на всю программу. И не более одного на каждую единицу трансляции.
И кстати, утверждение, что шаблоны неявно помечены inline - неверно. Но об этом позже.
За счет чего допускается возможность наличия нескольких определений сущности в программе? Если вы читали гайд по inline(можете найти в закрепе), то, наверняка, знаете ответ. За счет слабых символов.
Скомпилируем гццшкой самую простенькую функцию:
А утилитка nm покажет нам природу символов в бинаре. Там будет генерироваться оч много функций и символов связанных с вектором, поэтому разберем только один пример с конструктором, символ которого выглядит так:
0000000000000000 W std::vector<int, std::allocator<int> >::vector(unsigned long, std::allocator<int> const&)
Вот эта буковка W говорит, что этот символ - слабый. А слабые символы могут быть перезаписаны во время линковки. Линковщик просто сам выберет одно понравившееся ему определение из всех существующих в программе и перезапишет им остальные. Таким образом в программе останется всего одно определение шаблона и все будут ссылаться на него. Однако все равно до линковки во всех единицах трансляции будет своя копия интовой инстанциации шаблона.
Use your weaknesses to solve your problems. Stay cool.
#compiler #cppcore #template
Когда мы говорим про шаблоны и их линковку, нам важно видеть все детали общей картины. Иначе полное понимание так и не придет. Поэтому сегодня немного больше приоткроем линковочные тайны темплейтов.
Думаю, что не будет грубым обобщением сказать, что каждый из нас пользовался шаблонным классом std::vector. И это хорошо, все имеют опыт с ним и всем будет проще понимать, о чем сейчас пойдет речь.
Представим, что мы разрабатываем какое-то приложение или отдельный сервис. Этот сервис состоит из отдельных кодовых модулей, которые отдельно компилируются и линкуются вместе для получения готового бинарника. Так вот очень легко допустить, что во многих модулях одного и того же сервиса используется вектор интов. std::vector<int>. Обычно мы просто инклюдим в эти модули хэдэр вектора и после используем его. Таким образом происходит неявная инстанциация. То есть компилятор на этапе компиляции модулей сам инстанциацирует интовую специализацию вектора в каждой единице трансляции и использует ее в коде текущего юнита.
Однако, погодите-ка. То есть у нас в нескольких единицах трансляции есть одна и та же скомпилированная сущность и при линковке это не вызывает никаких проблем. Как так?
Вот, что стандарт говорит по поводу этого:
There can be more than one definition in a program of each of the following:
class type, enumeration type, inline function, inline variable(since C++17),
templated entity(template or member of template, but not full template specialization),
as long as all of the following is true[...]
Стандарт разрешает шаблонным сущностям иметь больше, чем одно определение на всю программу. И не более одного на каждую единицу трансляции.
И кстати, утверждение, что шаблоны неявно помечены inline - неверно. Но об этом позже.
За счет чего допускается возможность наличия нескольких определений сущности в программе? Если вы читали гайд по inline(можете найти в закрепе), то, наверняка, знаете ответ. За счет слабых символов.
Скомпилируем гццшкой самую простенькую функцию:
void foo() {
std::vector<int> vec(10);
vec[0] = 1;
}
А утилитка nm покажет нам природу символов в бинаре. Там будет генерироваться оч много функций и символов связанных с вектором, поэтому разберем только один пример с конструктором, символ которого выглядит так:
0000000000000000 W std::vector<int, std::allocator<int> >::vector(unsigned long, std::allocator<int> const&)
Вот эта буковка W говорит, что этот символ - слабый. А слабые символы могут быть перезаписаны во время линковки. Линковщик просто сам выберет одно понравившееся ему определение из всех существующих в программе и перезапишет им остальные. Таким образом в программе останется всего одно определение шаблона и все будут ссылаться на него. Однако все равно до линковки во всех единицах трансляции будет своя копия интовой инстанциации шаблона.
Use your weaknesses to solve your problems. Stay cool.
#compiler #cppcore #template
Шаблоны не подразумевают inline
Дисклеймер: в этом посте слово "специализация" будет значить конкретную программную сущность, объявленную через template<> с пустыми треугольными скобками, которая переопределяет поведения шаблона для конкретного типа.
В прошлом посте кратко коснулись этого. Сегодня разберемся в этом подробнее.
Мы уже знаем, что в программе может быть больше одного определения шаблона и это нормально. Ровно также может быть больше одного определения inline сущности. Так есть ли между этими утверждениями связь?
Очевидно, классы не могут быть inline. Разговор здесь пойдет только про inline функции и переменные(с С++14).
Во-первых, стандарт ничего не говорит по поводу того, что шаблоны по умолчанию inline. Хотя, например, для constexpr функций и статических полей класса это явно описано.
Во-вторых, в нем есть пара слов про явные специализации
Эта строчка говорит нам о том, что спецификаторы, которыми помечены явные специализации, могут не совпадать со спецификаторами самих шаблонов. Значит, что шаблоны имеет смысл помечать inline и мы даже может можем изменить это поведение в явной специализации. А значит, шаблоны не подразумевают inline. Их поведение только лишь схоже с inline сущностими в плане обхода ODR. Пример из стандарта:
Здесь нужно быть аккуратным, потому что на явные специализации распространяется ODR. Явные специализации - уже не шаблоны, поэтому, если вы хотите поместить их в хэдэр, то нужно помечать их inline, чтобы линковщик не ругался.
Если инлайн в нынешнее время в основном используется для обхода ODR, то есть ли смысл помечать шаблонные функции этим ключевым словом?
Особого смысла нет(помимо явных специализаций). Темплейты и так не подвержены ODR. А в остальном инлайн только лишь указывает компилятору, чтобы он сделал проверку на возможность inline expansion. Но он в принципе и так это делает для всех функций.
Differentiate things apart. Stay cool.
#template #cppcore #cpp14 #compiler
Дисклеймер: в этом посте слово "специализация" будет значить конкретную программную сущность, объявленную через template<> с пустыми треугольными скобками, которая переопределяет поведения шаблона для конкретного типа.
В прошлом посте кратко коснулись этого. Сегодня разберемся в этом подробнее.
Мы уже знаем, что в программе может быть больше одного определения шаблона и это нормально. Ровно также может быть больше одного определения inline сущности. Так есть ли между этими утверждениями связь?
Очевидно, классы не могут быть inline. Разговор здесь пойдет только про inline функции и переменные(с С++14).
Во-первых, стандарт ничего не говорит по поводу того, что шаблоны по умолчанию inline. Хотя, например, для constexpr функций и статических полей класса это явно описано.
Во-вторых, в нем есть пара слов про явные специализации
Whether an explicit specialization of a function or variable template is inline,
constexpr, constinit, or consteval is determined by the explicit specialization and
is independent of those properties of the template. Similarly, attributes appearing
in the declaration of a template have no effect on an explicit specialization of that
template...
Эта строчка говорит нам о том, что спецификаторы, которыми помечены явные специализации, могут не совпадать со спецификаторами самих шаблонов. Значит, что шаблоны имеет смысл помечать inline и мы даже может можем изменить это поведение в явной специализации. А значит, шаблоны не подразумевают inline. Их поведение только лишь схоже с inline сущностими в плане обхода ODR. Пример из стандарта:
template<class T> void f(T) { /* ... */ }
template<class T> inline T g(T) { /* ... */ }
template<> inline void f<>(int) { /* ... */ } // OK, inline
template<> int g<>(int) { /* ... */ } // OK, not inline
Здесь нужно быть аккуратным, потому что на явные специализации распространяется ODR. Явные специализации - уже не шаблоны, поэтому, если вы хотите поместить их в хэдэр, то нужно помечать их inline, чтобы линковщик не ругался.
Если инлайн в нынешнее время в основном используется для обхода ODR, то есть ли смысл помечать шаблонные функции этим ключевым словом?
Особого смысла нет(помимо явных специализаций). Темплейты и так не подвержены ODR. А в остальном инлайн только лишь указывает компилятору, чтобы он сделал проверку на возможность inline expansion. Но он в принципе и так это делает для всех функций.
Differentiate things apart. Stay cool.
#template #cppcore #cpp14 #compiler
Когда стоит использовать explicit template declaration
Мы поговорили о случае, в котором бесполезно использовать explicit template declaration. Теперь поговорим о наиболее уместном и логичном способе использования этой фичи.
Главная функция extern template - запретить компилятору неявную инстанциацию. Значит, для адекватного использования этой конструкции компилятору необходимо иметь возможность выполнить эту неявную инстанциацию. Единственным подходящим ситуации вариантом здесь будет нахождение полного определения шаблона в хэдэре, чтобы его могли видеть все заинтересованные лица(пофантизируйте в комментариях, как могло бы выглядеть лицо у единицы трансляции).
Дальше есть следующие 2 варианта - поместить все явные объявления инстанциации шаблона в этот же хэдэр и распихать по единицам трансляции. Как по мне, лучше иметь одну централизированную точку изменений, так как программисты - люди забывчивые и могут упустить момент добавления нового явного объявления и компилятор сам сделает неявную инстанциацию. Да и если помещать в разные места, то это приведет к дубликации кода. Поэтому оставляем extern template в хэдэре.
Ну и последний момент. Если есть явное объявление инстанциации, должно быть и ее явное определение. Причем это ВАЖНО. Нельзя при использовании extern template полагаться на неявную инстанциацию. В нашем случае это уже невозможно, потому что мы добавили в хэдэр с шаблоном запрет на неявную инстанциацию, но я все равно хочу на это обратить ваше внимание. Компилятор может ее оптимизировать, так что для нее больше не останется отдельно скомпилированной сущности и все вызовы просто встроятся. Тогда компановщик не сможет разрезолвить символы и будет undefined reference. Чуть позже расскажу об этом в отдельном посте.
Итак, explicit template instantiation. Мы помещаем явные определения всех нужных нам неявных специализаций в отдельный цппшник. И вот к коду в этой TU будет обращаться линкер, чтобы подставить адреса нужных вызовов. А в других TU не будет сгенерировано ничего связанного с шаблоном.
Продемонстрирую на примере:
Если мы отдельно скомпилируем main.cpp и посмотрим на символы объектника, то там будет только то, что связано с std::basic_string, но не с Ship. Как и было задумано.
Подводя итог: нам нужен хэдэр с полным определением шаблона и явными объявлениями extern template и сорец с явными определениями этих инстанциаций. Теперь мы можем везде тыкать наш хэдэр и ожидать уменьшения времени компиляции и меньшего размера объектников.
Choose the right way. Stay cool.
#template #compiler #cppcore
Мы поговорили о случае, в котором бесполезно использовать explicit template declaration. Теперь поговорим о наиболее уместном и логичном способе использования этой фичи.
Главная функция extern template - запретить компилятору неявную инстанциацию. Значит, для адекватного использования этой конструкции компилятору необходимо иметь возможность выполнить эту неявную инстанциацию. Единственным подходящим ситуации вариантом здесь будет нахождение полного определения шаблона в хэдэре, чтобы его могли видеть все заинтересованные лица(пофантизируйте в комментариях, как могло бы выглядеть лицо у единицы трансляции).
Дальше есть следующие 2 варианта - поместить все явные объявления инстанциации шаблона в этот же хэдэр и распихать по единицам трансляции. Как по мне, лучше иметь одну централизированную точку изменений, так как программисты - люди забывчивые и могут упустить момент добавления нового явного объявления и компилятор сам сделает неявную инстанциацию. Да и если помещать в разные места, то это приведет к дубликации кода. Поэтому оставляем extern template в хэдэре.
Ну и последний момент. Если есть явное объявление инстанциации, должно быть и ее явное определение. Причем это ВАЖНО. Нельзя при использовании extern template полагаться на неявную инстанциацию. В нашем случае это уже невозможно, потому что мы добавили в хэдэр с шаблоном запрет на неявную инстанциацию, но я все равно хочу на это обратить ваше внимание. Компилятор может ее оптимизировать, так что для нее больше не останется отдельно скомпилированной сущности и все вызовы просто встроятся. Тогда компановщик не сможет разрезолвить символы и будет undefined reference. Чуть позже расскажу об этом в отдельном посте.
Итак, explicit template instantiation. Мы помещаем явные определения всех нужных нам неявных специализаций в отдельный цппшник. И вот к коду в этой TU будет обращаться линкер, чтобы подставить адреса нужных вызовов. А в других TU не будет сгенерировано ничего связанного с шаблоном.
Продемонстрирую на примере:
// ship.hpp
#pragma once
#include <string>
template<typename T>
struct Ship
{
// contain some fields
void TurnShip(T command);
};
template <class T>
void Ship<T>::TurnShip(T command) {/* do stuff using command */}
extern template class Ship<std::string>; // text command
extern template class Ship<int>; // turn certain number of degrees clockwise
// ship.cpp
#include "ship.hpp"
template class Ship<std::string>;
template class Ship<int>;
// main.cpp
#include "ship.hpp"
#include <string>
int main() {
Ship<std::string> ship;
ship.TurnShip(std::string{"Turn upside down"});
Ship<int> ship1; // i know it's silly to instantiate 2 version of
// ship just to have a different style of turning,
// but stick to the goodold example
ship1.TurnShip(36'000); // just trying to make a giant whirlpool
}
Если мы отдельно скомпилируем main.cpp и посмотрим на символы объектника, то там будет только то, что связано с std::basic_string, но не с Ship. Как и было задумано.
Подводя итог: нам нужен хэдэр с полным определением шаблона и явными объявлениями extern template и сорец с явными определениями этих инстанциаций. Теперь мы можем везде тыкать наш хэдэр и ожидать уменьшения времени компиляции и меньшего размера объектников.
Choose the right way. Stay cool.
#template #compiler #cppcore
Встраивание шаблонов
Небольшое предисловие: один из админов и автор последующих 5 постов уходит в отпуск на первые майские и будет недоступен. Выходить они будут отложенными публикациями. Так как мы особо не влияем на тексты друг друга, то второй админ может быть спокойно застан врасплох возможными неточностями постов и вашими комментариями. Так что прошу иметь это ввиду. Спасибо
Вот здесь мы поговорили о том, что методы класса - это по факту те же самые обычные функции, только для них первым параметром передается this. И если подумать 1.34 секунды, то можно понять, что взаимодействие класса с внешним миром происходит только за счет методов. А поля класса - это просто кусок памяти, из которого в разных ситуациях компилятор может достать ту или иную информацию. Получается, что низкоуровневый "код класса" - это набор низкоуровневого кода его методов(то есть обычных функций) и не более.
Получается, что возможна ситуация, когда компилятор встроит вызовы одного, нескольких или всех методов класса.
Шаблонные классы - хоть и неполноценные классы, но их инстанциации - да. Поэтому их методы также могут инлайниться, никаких исключений.
Обычные функции тоже могут встраиваться.
А константные шаблонные переменные после инстанциации могут не иметь имени, компилятор просто сразу подставит во все места использования конкретное значение.
Итого, получается, что у нас все шаблонные сущности могут быть встроены компилятором. Конечно же для этого должны быть включены оптимизации(но и без них может получиться).
Получается, что если мы в какой-то единице трансляции указываем явное объявление инстанциации с помощью extern template, и рассчитываем на неявную инстанциацию в другой единице трансляции, то мы спокойно можем нарваться на undefined reference.
Происходит это примерно так:
Знакомый пример, только пара модификаций. В хэдэре только объявление и определение шаблона. В ship.cpp пытаемся неявно инстанцировать строковую специализацию. Чтобы компилятор полностью не убирал код внутри foo за ненадобностью(тогда и ничего инстанцировать не нужно будет), сделаем так, чтобы она влияла на внешний мир. Добавим в шаблон поле, в методе его будем инкрементировать, и в foo выведем поле после модификации. В мэйне будем полагаться на инстанциацию в другой единице трансляции за счет extern template.
Вот если это попытаться скомпилировать(с оптимизациями) и собрать, то на линковке произойдет undefined reference. Компилятор увидел, что метод TurnShip слишком простой и его спокойно можно встроить и не генерировать для него определение. Что и происходит. А линкер в свою очередь из-за этого и не смог найти определение метода.
А божественным избавлением от этой проказы будет использование явной инстанциации. Она заставляет компилятор сгенерировать определение символа. Вызовы по прежнему могут инлайниться, но определение будет и мы сможем к нему обращаться.
Так что помните простое правило: на любое явное объявление инстанциации обязательно нужно предоставить явное определение инстанциации(1 на всю программу на каждое конкретное определение).
Rely on explicitly stated things. Stay cool.
#cppcore #template #compiler
Небольшое предисловие: один из админов и автор последующих 5 постов уходит в отпуск на первые майские и будет недоступен. Выходить они будут отложенными публикациями. Так как мы особо не влияем на тексты друг друга, то второй админ может быть спокойно застан врасплох возможными неточностями постов и вашими комментариями. Так что прошу иметь это ввиду. Спасибо
Вот здесь мы поговорили о том, что методы класса - это по факту те же самые обычные функции, только для них первым параметром передается this. И если подумать 1.34 секунды, то можно понять, что взаимодействие класса с внешним миром происходит только за счет методов. А поля класса - это просто кусок памяти, из которого в разных ситуациях компилятор может достать ту или иную информацию. Получается, что низкоуровневый "код класса" - это набор низкоуровневого кода его методов(то есть обычных функций) и не более.
Получается, что возможна ситуация, когда компилятор встроит вызовы одного, нескольких или всех методов класса.
Шаблонные классы - хоть и неполноценные классы, но их инстанциации - да. Поэтому их методы также могут инлайниться, никаких исключений.
Обычные функции тоже могут встраиваться.
А константные шаблонные переменные после инстанциации могут не иметь имени, компилятор просто сразу подставит во все места использования конкретное значение.
Итого, получается, что у нас все шаблонные сущности могут быть встроены компилятором. Конечно же для этого должны быть включены оптимизации(но и без них может получиться).
Получается, что если мы в какой-то единице трансляции указываем явное объявление инстанциации с помощью extern template, и рассчитываем на неявную инстанциацию в другой единице трансляции, то мы спокойно можем нарваться на undefined reference.
Происходит это примерно так:
// ship.hpp
#pragma once
template<typename T>
struct Ship
{
int i = 0;
void TurnShip(T command);
};
template <class T>
void Ship<T>::TurnShip(T command) {i++;}
// ship.cpp
#include "ship.hpp"
#include <string>
#include <iostream>
void foo() {
Ship<std::string> ship{};
ship.TurnShip(std::string{"Turn upside down"});
std::cout << ship.i << std::endl;
}
// main.cpp
#include "ship.hpp"
#include <string>
extern template class Ship<std::string>;
int main() {
Ship<std::string> ship;
ship.TurnShip(std::string{"Turn upside down"});
}
Знакомый пример, только пара модификаций. В хэдэре только объявление и определение шаблона. В ship.cpp пытаемся неявно инстанцировать строковую специализацию. Чтобы компилятор полностью не убирал код внутри foo за ненадобностью(тогда и ничего инстанцировать не нужно будет), сделаем так, чтобы она влияла на внешний мир. Добавим в шаблон поле, в методе его будем инкрементировать, и в foo выведем поле после модификации. В мэйне будем полагаться на инстанциацию в другой единице трансляции за счет extern template.
Вот если это попытаться скомпилировать(с оптимизациями) и собрать, то на линковке произойдет undefined reference. Компилятор увидел, что метод TurnShip слишком простой и его спокойно можно встроить и не генерировать для него определение. Что и происходит. А линкер в свою очередь из-за этого и не смог найти определение метода.
А божественным избавлением от этой проказы будет использование явной инстанциации. Она заставляет компилятор сгенерировать определение символа. Вызовы по прежнему могут инлайниться, но определение будет и мы сможем к нему обращаться.
Так что помните простое правило: на любое явное объявление инстанциации обязательно нужно предоставить явное определение инстанциации(1 на всю программу на каждое конкретное определение).
Rely on explicitly stated things. Stay cool.
#cppcore #template #compiler
Не всегда инстанциация шаблона нужна для работы программы
Возьмем пример из прошлого поста, объединим в хэдэре объявление шаблона с его определением и выкинем ship.cpp. И попробуем скомпилировать только main.cpp.
И неожиданно, все компилируется и выводится единичка. Почему так? Мы ведь почти ничего не поменяли даже просто нагло и беспардонно выкинули так нам необходимую единицу трансляции с явным инстанированием. Как это работает?
Дело в том, что любой метод, определенный внутри описания класса, неявно помечается inline. А на инлайн сущности не работает эффект подавления неявной специализации. Стандарт вот что говорит об этом:
Кажется, тут можно такую цепочку мыслей провести: компилятору запрещается делать неявную инстанциацию строкового корабля. Но он ее может и не делать, а просто встроить вызов метода этой инстанциации внутрь функции main и дело в шляпе! И ничего не нарушили и все работает.
Естественно, на это полагаться нельзя, потому что не любой метод может быть встроен, а значит компилятору придется проводить неявную инстанциацию. А мы как раз и добивались, чтобы этого не было. И правило "на любое явное объявление инстанциации обязательно нужно предоставить явное определение инстанциации" по-прежнему работает.
Просто интересно было показать, как такое небольшое изменение может развернуть ситуацию на 180. И кстати, если все-таки держать отдельно описание класса и его определение, но пометить метод inline, то будет тот же эффект, который я описал выше.
Pay attention to small details. Stay cool.
#template #compiler #cppcore
Возьмем пример из прошлого поста, объединим в хэдэре объявление шаблона с его определением и выкинем ship.cpp. И попробуем скомпилировать только main.cpp.
// ship.hpp
#pragma once
template<typename T>
struct Ship
{
int i = 0;
void TurnShip(T command) {i++;}
};
// main.cpp
#include "ship.hpp"
#include <string>
#include <iostream>
extern template class Ship<std::string>;
int main() {
Ship<std::string> ship;
ship.TurnShip(std::string{"Turn upside down"});
std::cout << ship.i << std::endl;
}
И неожиданно, все компилируется и выводится единичка. Почему так? Мы ведь почти ничего не поменяли даже просто нагло и беспардонно выкинули так нам необходимую единицу трансляции с явным инстанированием. Как это работает?
Дело в том, что любой метод, определенный внутри описания класса, неявно помечается inline. А на инлайн сущности не работает эффект подавления неявной специализации. Стандарт вот что говорит об этом:
Except for inline functions and class template specializations,
explicit instantiation declarations have the effect of suppressing
the implicit instantiation of the entity to which they refer.
Кажется, тут можно такую цепочку мыслей провести: компилятору запрещается делать неявную инстанциацию строкового корабля. Но он ее может и не делать, а просто встроить вызов метода этой инстанциации внутрь функции main и дело в шляпе! И ничего не нарушили и все работает.
Естественно, на это полагаться нельзя, потому что не любой метод может быть встроен, а значит компилятору придется проводить неявную инстанциацию. А мы как раз и добивались, чтобы этого не было. И правило "на любое явное объявление инстанциации обязательно нужно предоставить явное определение инстанциации" по-прежнему работает.
Просто интересно было показать, как такое небольшое изменение может развернуть ситуацию на 180. И кстати, если все-таки держать отдельно описание класса и его определение, но пометить метод inline, то будет тот же эффект, который я описал выше.
Pay attention to small details. Stay cool.
#template #compiler #cppcore
Преимущества использования extern template
C++ известен долгой компиляцией программ. И одной из причин такого поведения является наличие шаблонов в языке. За счет того, что стандарт допускает больше одной конкретной инстанциации шаблона в программе, люди сильно расслабляются. Мало кто знает, как правильно организовывать и использовать шаблонный код. В большинстве случаев он просто находится в хэдэре и подключается во всевозможные места. Соответсвенно, в каждой TU, куда подключается хэдэр, будет своя копия инстанциации. Но это же не просто сама копия, на компиляцию всех копий тратится время.
А если используются какие-нибудь кодогенераторы, типа grpc-шного или soap-ного, то там реально может быть очень много единиц трансляции. И в каждой будет своя копия какого-нибудь вектора или опшинала.
И хотя для STL-ных сущностей extern template ничем не поможет(об этом в другом посте), для самописных шаблонов, расположенных в хэдэрах - подойдет. Адекватную организацию кода при использовании extern template, мы осветили тут https://t.me/grokaemcpp/226.
Но какие конкретно преимущества дает такой способ организации шаблонного кода?
1) Уменьшение размеров объектных файлов скомпилированных единиц трансляции. При сборке больших проектов у вас скорее всего сгенерируются сотни, если не тысячи объектных файлов или либок, которые за счет загромождения инстанциациями могут весить десятки и сотни мегабайт. Не каждый себе может позволить на машинке такой билд, который помимо полезных исполняемых файлов будет содержать огромное количество объектников с дублирующимся миллион раз кодом. extern template запрещает неявную инстанциацию шаблонов, а значит среди всех объектников будет только один, который и будет содержать нужную инстанциацию.
2) Уменьшение времени компиляции. Компилятору запретили генерировать код, а значит много лишних действий убирается и сокращается время компиляции.
3) Сокращение времени линковки. Вот это не прям очевидный пункт. Чем объемнее объектные файлы - тем больше линкеру работы. А учитывая, что для дедуплицирования инстанциаций нужно найти эти одинаковые дубли, сопоставить их, выбрать один и убрать все остальные, то задача уже не кажется такой простой.
4) Любой код, который подключит ваш заголовочник, сможет инстанцировать или найти любой explicit template instantiation, который захочет. Через extern template мы запретим компилятору самостоятельно генерировать эти инстанциации. Но если для какого-то шаблонного параметра не будет прописан extern template, то компилятор сможет сам неявно конкретизировать шаблон с этим параметром. Поэтому в этом плане, это очень гибкий инструмент.
Из-за последнего пункта такой способ организации кода подойдет для частоиспользуемых шаблонов, которые инстанцируются с большим многообразием параметров. Просто в цппшнике можно предоставить самые популярные варианты инстанциаций, которые будут давать большой импакт в увеличении времени компиляции и наиболее часто будут дублироваться. Остальное компилятор сможет сам неявно инстанцировать.
Если внешняя библиотека завязана на ваш шаблон, то это единственный способ адекватно предоставить ей доступ к коду.
Однако есть способ организации кода, при котором будут все те же плюсы, плюс еще плюсы, минус лишней работы, но потеряем немного гибкости. Но об этом завтра.
Find benefits of using different things in various situations. Stay cool.
#cppcore #template #compiler
C++ известен долгой компиляцией программ. И одной из причин такого поведения является наличие шаблонов в языке. За счет того, что стандарт допускает больше одной конкретной инстанциации шаблона в программе, люди сильно расслабляются. Мало кто знает, как правильно организовывать и использовать шаблонный код. В большинстве случаев он просто находится в хэдэре и подключается во всевозможные места. Соответсвенно, в каждой TU, куда подключается хэдэр, будет своя копия инстанциации. Но это же не просто сама копия, на компиляцию всех копий тратится время.
А если используются какие-нибудь кодогенераторы, типа grpc-шного или soap-ного, то там реально может быть очень много единиц трансляции. И в каждой будет своя копия какого-нибудь вектора или опшинала.
И хотя для STL-ных сущностей extern template ничем не поможет(об этом в другом посте), для самописных шаблонов, расположенных в хэдэрах - подойдет. Адекватную организацию кода при использовании extern template, мы осветили тут https://t.me/grokaemcpp/226.
Но какие конкретно преимущества дает такой способ организации шаблонного кода?
1) Уменьшение размеров объектных файлов скомпилированных единиц трансляции. При сборке больших проектов у вас скорее всего сгенерируются сотни, если не тысячи объектных файлов или либок, которые за счет загромождения инстанциациями могут весить десятки и сотни мегабайт. Не каждый себе может позволить на машинке такой билд, который помимо полезных исполняемых файлов будет содержать огромное количество объектников с дублирующимся миллион раз кодом. extern template запрещает неявную инстанциацию шаблонов, а значит среди всех объектников будет только один, который и будет содержать нужную инстанциацию.
2) Уменьшение времени компиляции. Компилятору запретили генерировать код, а значит много лишних действий убирается и сокращается время компиляции.
3) Сокращение времени линковки. Вот это не прям очевидный пункт. Чем объемнее объектные файлы - тем больше линкеру работы. А учитывая, что для дедуплицирования инстанциаций нужно найти эти одинаковые дубли, сопоставить их, выбрать один и убрать все остальные, то задача уже не кажется такой простой.
4) Любой код, который подключит ваш заголовочник, сможет инстанцировать или найти любой explicit template instantiation, который захочет. Через extern template мы запретим компилятору самостоятельно генерировать эти инстанциации. Но если для какого-то шаблонного параметра не будет прописан extern template, то компилятор сможет сам неявно конкретизировать шаблон с этим параметром. Поэтому в этом плане, это очень гибкий инструмент.
Из-за последнего пункта такой способ организации кода подойдет для частоиспользуемых шаблонов, которые инстанцируются с большим многообразием параметров. Просто в цппшнике можно предоставить самые популярные варианты инстанциаций, которые будут давать большой импакт в увеличении времени компиляции и наиболее часто будут дублироваться. Остальное компилятор сможет сам неявно инстанцировать.
Если внешняя библиотека завязана на ваш шаблон, то это единственный способ адекватно предоставить ей доступ к коду.
Однако есть способ организации кода, при котором будут все те же плюсы, плюс еще плюсы, минус лишней работы, но потеряем немного гибкости. Но об этом завтра.
Find benefits of using different things in various situations. Stay cool.
#cppcore #template #compiler
Другой способ организации шаблонного кода
В чем недостаток способа из предыдущего поста? В том, что при любом изменении шаблона, придется перекомпилировать все единицы трансляции, которые его включают. Весь код находится в хэдэре, значит TU будет в себе содержать полное определение сущности. Значит, любое незначительное изменение реализации приводит к перекомпиляции.
Не зря люди придумали разделение кода на объявление сущности в хэдэре и ее реализацию в цппшнике. Внешний интерфейс класса/сигнатура функции меняются не так часто. А вот изменение деталей реализации при разработке - дело само собой разумеющееся и это происходит на каждой итерации билда при отладке кода.
При использовании подхода с разделением на хэдэр и сорец, при изменении реализации мы перекомпилируем только сорец. И все остальные TU не будут нуждаться в перекомпилировании. А при линковке они просто будут обращаться за нужными символами в обновленную TU исходников сущности.
Таким образом, мы хоть и не увеличиваем изначальную скорость компиляции, но увеличиваем ее при перекомпиляции исходников во время отладки. Я считаю, что это даже больший плюс. Потому что при отладке кода, мы его мильён раз изменяем и на каждую попытку ждать несколько минут - это зашквар. Так программисты и спиваются. Надо же что-то делать пока билд собирается?
Плюс из-за помещения определения в хэдэр, вы не можете распространять свои исходники без раскрытия деталей реализации. Код - бедная, тонкая, голая и стесняющаяся чужих людских глаз натура. Зачем вы его показываете на общее обозрение? Уважьте малыша. Спрячьте его.
Еще один плюс - удобство чтения кода и его организации. Очень часто нам нужно узнать только публичный интерфейс класса, без погружения в детали. Заголовочник с одним объявлением сущности сильно помогает в этом.
Ну и в мире коммерческой разработки на С++ в принципе принято разделение на сорцы и хэдэры. Поэтому, зачастую, инфраструктура проекта завязвна на этой негласной договоренности. И часто бывает непонятно, куда запихать обособленный заголовочник.
Единственный способ, при котором мы можем использовать разделение объявления и определения по разным файлам с шаблонами - мы должны точно знать, с какими параметрами будет инстанцироваться наш шаблон и в цппшнике предоставить явное его инстанцирование с этими параметрами.
И это большое ограничение. Но часто ли вы пишете шаблонный код, у которого вы прям не знаете полный набор возможных шаблонных параметров на данный момент? Средний разработчик не так часто это делает. И вот во всех случаях, когда вы на данный момент точно знаете полное множество возможных шаблонных параметров, стоит использовать именно тот способ, который описан в предыдущем абзаце или в этом посте.
И не нужно нигде писать никакой extern! Компилятор из объявления сам ничего не может инстанцировать, поэтому главная задача extern template решается автоматически.
Вы мне скажете, что нужно будет постоянно следить за новопоявившимися параметрами и добавлять явные инстанциации шаблона с ними в цппшник. И я вам отвечу: это правда. Просто в этом случае следить очень просто: у вас перестает компилироваться код)
Однако это лучше, чем в подходе в extern. Если здесь не уследить за новым шаблонным параметров, то компилятор сам сможет неявно с ним инстанцировать шаблон и тогда будут проявляться все негативные эффекты, которых мы и хотели избежать, использовав extern template.
Последние 2 поста - кульминация всей серии и реальная практическая выжимка из всего того, что мы уже обсудили. Выбирайте то, что подходит вашей ситуации. Надеюсь, я вас убедил в пользе явной инстанциации шаблонов. Еще один пост и мы заканчиваем этувсем уже немного поднадоевшую долгую историю.
Choose the proper tool. Stay cool.
#template #compiler #cppcore
В чем недостаток способа из предыдущего поста? В том, что при любом изменении шаблона, придется перекомпилировать все единицы трансляции, которые его включают. Весь код находится в хэдэре, значит TU будет в себе содержать полное определение сущности. Значит, любое незначительное изменение реализации приводит к перекомпиляции.
Не зря люди придумали разделение кода на объявление сущности в хэдэре и ее реализацию в цппшнике. Внешний интерфейс класса/сигнатура функции меняются не так часто. А вот изменение деталей реализации при разработке - дело само собой разумеющееся и это происходит на каждой итерации билда при отладке кода.
При использовании подхода с разделением на хэдэр и сорец, при изменении реализации мы перекомпилируем только сорец. И все остальные TU не будут нуждаться в перекомпилировании. А при линковке они просто будут обращаться за нужными символами в обновленную TU исходников сущности.
Таким образом, мы хоть и не увеличиваем изначальную скорость компиляции, но увеличиваем ее при перекомпиляции исходников во время отладки. Я считаю, что это даже больший плюс. Потому что при отладке кода, мы его мильён раз изменяем и на каждую попытку ждать несколько минут - это зашквар. Так программисты и спиваются. Надо же что-то делать пока билд собирается?
Плюс из-за помещения определения в хэдэр, вы не можете распространять свои исходники без раскрытия деталей реализации. Код - бедная, тонкая, голая и стесняющаяся чужих людских глаз натура. Зачем вы его показываете на общее обозрение? Уважьте малыша. Спрячьте его.
Еще один плюс - удобство чтения кода и его организации. Очень часто нам нужно узнать только публичный интерфейс класса, без погружения в детали. Заголовочник с одним объявлением сущности сильно помогает в этом.
Ну и в мире коммерческой разработки на С++ в принципе принято разделение на сорцы и хэдэры. Поэтому, зачастую, инфраструктура проекта завязвна на этой негласной договоренности. И часто бывает непонятно, куда запихать обособленный заголовочник.
Единственный способ, при котором мы можем использовать разделение объявления и определения по разным файлам с шаблонами - мы должны точно знать, с какими параметрами будет инстанцироваться наш шаблон и в цппшнике предоставить явное его инстанцирование с этими параметрами.
И это большое ограничение. Но часто ли вы пишете шаблонный код, у которого вы прям не знаете полный набор возможных шаблонных параметров на данный момент? Средний разработчик не так часто это делает. И вот во всех случаях, когда вы на данный момент точно знаете полное множество возможных шаблонных параметров, стоит использовать именно тот способ, который описан в предыдущем абзаце или в этом посте.
И не нужно нигде писать никакой extern! Компилятор из объявления сам ничего не может инстанцировать, поэтому главная задача extern template решается автоматически.
Вы мне скажете, что нужно будет постоянно следить за новопоявившимися параметрами и добавлять явные инстанциации шаблона с ними в цппшник. И я вам отвечу: это правда. Просто в этом случае следить очень просто: у вас перестает компилироваться код)
Однако это лучше, чем в подходе в extern. Если здесь не уследить за новым шаблонным параметров, то компилятор сам сможет неявно с ним инстанцировать шаблон и тогда будут проявляться все негативные эффекты, которых мы и хотели избежать, использовав extern template.
Последние 2 поста - кульминация всей серии и реальная практическая выжимка из всего того, что мы уже обсудили. Выбирайте то, что подходит вашей ситуации. Надеюсь, я вас убедил в пользе явной инстанциации шаблонов. Еще один пост и мы заканчиваем эту
Choose the proper tool. Stay cool.
#template #compiler #cppcore
extern template с шаблонами STL
Обещал рассказать, почему вы не сможете подавить неявную инстанциацию для STL сущностей. Это будет хорошим завершением серии, потому что включает множество обсужденных концепций и особенностей.
Оговорюсь, что буду говорить за реализацию стандартной библиотеки от gcc.
Начнем с того, что фичи стандартных шаблонов распространяются и подключаются с помощью хэдэр-файлов. И если внимательно посмотреть на эти хэдэра, то мы можем увидеть, что авторы библиотеки помещают методы шаблонных классов внутрь определения классов(за все-превсе классы не могу сказать, но тенденция явно проглядывается).
Далее. Единственным способом запретить неявное инстанцирование при распространении кода с помощью заголовочников - с помощью extern template. Все конкретизации мы не можем запретить, но самые популярные - можем попробовать. В случае успеха это будет давать большой импакт к уменьшению времени компиляции и размеру объектников.
А в недавнем посте "Не всегда инстанциация шаблона нужна для работы программы" мы поговорили о том, что методы, определенные внутри описания класса, неявно помечаются inline. А для inline функций запрет на неявное инстанцирование не работает!
Поэтому для шаблонов STL(под этим акронимом я понимаю все шаблонные классы стандартной библиотеки) вы просто не сможете получить никакого профита от extern template. Стандарт это явно запрещает.
Получается, что все разговоры про запрет или ограничение неявного инстанцирования касаются лишь только кастомного кода. Ограничения на стандартные инструменты наложить не получится, как и поиметь соотвествующих плюшек. Sad, but true. Об этой особенности очень мало говорят даже в англоязычном пространстве. Поэтому вы теперь элита и эксперты в правильной организации шаблонного кода.
Use your tools in proper places. Stay cool.
#cpcore #template #compiler
Обещал рассказать, почему вы не сможете подавить неявную инстанциацию для STL сущностей. Это будет хорошим завершением серии, потому что включает множество обсужденных концепций и особенностей.
Оговорюсь, что буду говорить за реализацию стандартной библиотеки от gcc.
Начнем с того, что фичи стандартных шаблонов распространяются и подключаются с помощью хэдэр-файлов. И если внимательно посмотреть на эти хэдэра, то мы можем увидеть, что авторы библиотеки помещают методы шаблонных классов внутрь определения классов(за все-превсе классы не могу сказать, но тенденция явно проглядывается).
Далее. Единственным способом запретить неявное инстанцирование при распространении кода с помощью заголовочников - с помощью extern template. Все конкретизации мы не можем запретить, но самые популярные - можем попробовать. В случае успеха это будет давать большой импакт к уменьшению времени компиляции и размеру объектников.
А в недавнем посте "Не всегда инстанциация шаблона нужна для работы программы" мы поговорили о том, что методы, определенные внутри описания класса, неявно помечаются inline. А для inline функций запрет на неявное инстанцирование не работает!
Поэтому для шаблонов STL(под этим акронимом я понимаю все шаблонные классы стандартной библиотеки) вы просто не сможете получить никакого профита от extern template. Стандарт это явно запрещает.
Получается, что все разговоры про запрет или ограничение неявного инстанцирования касаются лишь только кастомного кода. Ограничения на стандартные инструменты наложить не получится, как и поиметь соотвествующих плюшек. Sad, but true. Об этой особенности очень мало говорят даже в англоязычном пространстве. Поэтому вы теперь элита и эксперты в правильной организации шаблонного кода.
Use your tools in proper places. Stay cool.
#cpcore #template #compiler