Обычно при объявлении класса C ++ рекомендуется помещать только объявление в заголовочный файл и помещать реализацию в исходный файл. Однако, похоже, что эта модель проектирования не работает для шаблонных классов.
При поиске в Интернете, кажется, есть 2 мнения о лучшем способе управления шаблонами классов:
1. Вся декларация и реализация в заголовке.
Это довольно просто, но приводит к тому, что, на мой взгляд, трудно поддерживать и редактировать файлы кода, когда шаблон становится большим.
2. Запишите реализацию в шаблон включаемого файла (.tpp), включенный в конце.
Мне кажется, что это лучшее решение, но оно не находит широкого применения. Есть ли причина, по которой этот подход уступает?
Я знаю, что много раз стиль кода продиктован личными предпочтениями или традиционным стилем. Я начинаю новый проект (перенос старого C-проекта на C ++), и я относительно новичок в разработке ОО и хотел бы следовать передовым методам с самого начала.
источник
Ответы:
При написании шаблонного класса C ++ у вас обычно есть три варианта:
(1) Поместите объявление и определение в заголовок.
или
Pro:
Против:
Foo
как член, вам необходимо включитьfoo.h
. Это означает, что изменение реализацииFoo::f
распространяется как через заголовочные, так и исходные файлы.Давайте подробнее рассмотрим влияние перестройки: для не шаблонных классов C ++ вы помещаете объявления в .h и определения методов в .cpp. Таким образом, при изменении реализации метода необходимо перекомпилировать только один .cpp. Это отличается для шаблонных классов, если .h содержит весь ваш код. Посмотрите на следующий пример:
Здесь единственное использование
Foo::f
внутриbar.cpp
. Однако, если вы измените реализациюFoo::f
, и то,bar.cpp
и другоеqux.cpp
необходимо перекомпилировать. РеализацияFoo::f
живет в обоих файлах, хотя ни одна из частейQux
напрямую не использует ничего изFoo::f
. Для крупных проектов это может скоро стать проблемой.(2) Поместите объявление в .h, а определение в .tpp и включите его в .h.
Pro:
Против:
Это решение разделяет объявление и определение метода в двух отдельных файлах, подобно .h / .cpp. Однако этот подход имеет ту же проблему перестройки, что и (1) , потому что заголовок напрямую включает определения методов.
(3) Поместить объявление в .h и определение в .tpp, но не включать .tpp в .h.
Pro:
Против:
Foo
члена в классBar
, вы должны включитьfoo.h
в заголовок. Если вы звонитеFoo::f
в .cpp, вы также должны включитьfoo.tpp
туда.Такой подход уменьшает влияние перестроения, поскольку
Foo::f
необходимо перекомпилировать только те файлы .cpp, которые действительно используются . Однако это имеет свою цену: все эти файлы должны быть включеныfoo.tpp
. Возьмите пример сверху и используйте новый подход:Как видите, единственное отличие заключается в дополнительном включении
foo.tpp
вbar.cpp
. Это неудобно, и добавление второго включения для класса в зависимости от того, вызываете ли вы методы для него, кажется очень уродливым. Тем не менее, вы уменьшаете влияние перестроения:bar.cpp
требуется перекомпиляция только при изменении реализацииFoo::f
. Файл неqux.cpp
нуждается в перекомпиляции.Резюме:
Если вы внедряете библиотеку, вам обычно не нужно заботиться о последствиях перестройки. Пользователи вашей библиотеки берут релиз и используют его, и реализация библиотеки не меняется в повседневной работе пользователя. В таких случаях библиотека может использовать подход (1) или (2), и выбор языка зависит от вкуса.
Однако, если вы работаете с приложением или работаете с внутренней библиотекой вашей компании, код часто меняется. Таким образом, вы должны заботиться о восстановлении воздействия. Выбор подхода (3) может быть хорошим вариантом, если вы заставите своих разработчиков принять дополнительное включение.
источник
Подобно
.tpp
идее (которую я никогда не использовал), мы помещаем большинство встроенных функций в-inl.hpp
файл, который включается в конец обычного.hpp
файла.Как показывают другие, это делает интерфейс читаемым, перемещая беспорядок встроенных реализаций (таких как шаблоны) в другой файл. Мы допускаем некоторые встроенные интерфейсы, но стараемся ограничить их небольшими, обычно однострочными функциями.
источник
Одна про монета второго варианта в том, что ваши заголовки выглядят более аккуратно.
Кон может быть, у вас может быть встроенная проверка ошибок IDE, и привязки отладчика облажались.
источник
Я очень предпочитаю подход, заключающийся в том, чтобы поместить реализацию в отдельный файл и иметь только документацию и объявления в заголовочном файле.
Возможно, причина того, что вы не видели такого подхода на практике, заключается в том, что вы не смотрели в нужных местах ;-)
Или - возможно, потому что это требует немного дополнительных усилий при разработке программного обеспечения. Но для библиотеки классов, эти усилия стоят ПОЛНОСТЬЮ, ИМХО, и окупаются в гораздо более простой в использовании / чтении библиотеке.
Возьмите эту библиотеку для примера: https://github.com/SophistSolutions/Stroika/
Вся библиотека написана с использованием этого подхода, и если вы посмотрите код, вы увидите, насколько хорошо он работает.
Заголовочные файлы примерно такие же, как файлы реализации, но они заполнены только декларациями и документацией.
Сравните удобочитаемость Stroika с вашей любимой реализацией std c ++ (gcc или libc ++ или msvc). Все они используют встроенный подход реализации в заголовке, и, хотя они написаны очень хорошо, ИМХО, они не так читаемы.
источник