#c #header #coding-style #project-structure
#c #заголовок #стиль кодирования #структура проекта
Вопрос:
Я работаю над очень маленьким фрагментом исходного кода C / C . Программа считывает входные значения из stdin, обрабатывает их с помощью алгоритма и записывает результаты в стандартный вывод.
Я бы просто реализовал все это в одном файле, но мне также нужны тестовые примеры для алгоритма (не для чтения ввода / вывода), поэтому в моем проекте есть следующие файлы:
- main.cpp
- сортировка.hpp
- sort_test.cpp
Я сразу же реализую алгоритм в sort.hpp, нет sort.cpp . Он довольно короткий и не имеет никаких зависимостей.
Вы бы сказали, что в некоторых случаях функции, определенные в заголовках, подходят, даже если они представляют собой сложные алгоритмы, а не просто средства доступа / мутаторы? Или есть причина, по которой я должен избегать этого? Когда я должен переместить код из заголовка в исходный файл?
Комментарии:
1. Вероятно, лучше в programmers.stackexchange.com . Я бы сказал, что любая реализация, длина которой превышает строку кода, должна находиться в файле .cpp. Возможно, было бы немного лучше включить объявление в файл .cpp, чем затем реализовать в файле .hpp!
2. @Джеймс: что, почему? Я не понимаю, почему это лучше подходило бы для Programmers.SE
3. Для вашего тестового кода (
sort_test.cpp
) вы могли бы рассмотреть возможность включенияsort.cpp
напрямую, чтобы вы могли протестировать его внутренние функции.4. Что это за «встроенные» функции? Не нужно было этого делать, работает нормально. Это просто свободные функции, без класса, без пространства имен, ничего. Однако только один cpp-файл в проекте использует его. (main.cpp для программы, sort_test.cpp для тестового примера)
5. Думаю, я просто сделаю это встроенным. Не ожидаю странной ошибки компилятора, как только я использую это из более чем одного файла.
Ответ №1:
Нет ничего плохого в наличии функций в файлах заголовков, если вы понимаете компромисс. Помещение их в файл заголовка означает, что их придется компилировать (и перекомпилировать) в любой модуль перевода, который включает заголовок. (и они должны быть объявлены inline
, иначе вы получите ошибки компоновщика.)
В проектах с большим количеством единиц перевода это может привести к заметному замедлению времени компиляции, если вы делаете это часто.
С другой стороны, это гарантирует, что определение функции видно везде, где вызывается функция, — а это означает, что его можно тривиально встроить, так что результирующая программа может выполняться быстрее.
И, наконец, с шаблонами функций у вас обычно нет реальной альтернативы. Определение должно быть видимым на сайте вызова, и единственный практический способ добиться этого — поместить его в заголовок.
Последнее соображение заключается в том, что библиотеки только для заголовков проще развертывать и использовать. Вам не нужно ни с чем связываться, вам не нужно беспокоиться об ABI или о чем-либо еще. Вы просто добавляете заголовки в свой проект, включаете их и все готово.
Довольно много популярных библиотек используют стратегию только для заголовков.
Ответ №2:
Когда вы помещаете функции в заголовки, вы должны убедиться, что они объявлены inline
. Это требуется, чтобы избежать предупреждения о дублировании определения, когда более одного файла .cpp включают этот файл заголовка. Как правило, вы должны помещать только небольшие функции внутри файлов заголовков, потому что они будут скомпилированы для каждого cpp-файла, который включает заголовок, что замедлит время компиляции, а также приведет к раздуванию кода; исполняемый файл большего размера.
Ответ №3:
Можно поместить любую функцию в заголовок, если это inline
. Такие вещи, как функции, определенные внутри class { }
, и шаблоны неявно inline
.
Если результирующее приложение становится слишком большим, оптимизируйте размер кода. Оптимизация до возникновения проблемы — это антишаблон, особенно когда есть преимущество в том, чтобы делать это «по-своему», и исправить это так же просто, как переместить из одного файла в другой и стереть inline
.
Конечно, если вы хотите распространять код в виде библиотеки, то выбор между заголовком, статической библиотекой или двоичным файлом динамической библиотеки является важным решением, влияющим на пользователей.
Ответ №4:
Подавляющее большинство библиотек boost предназначены только для заголовков, поэтому я бы сказал: да, это устоявшаяся и принятая практика. Просто не забудьте inline
.
Комментарии:
1. Спасибо, что указали мне на Boost. Похоже, они действительно пишут 90% своего кода в заголовках 🙂
Ответ №5:
Это действительно правильный выбор. Но включение этого в заголовок означает, что это будет встроенный код, а не функция. Если вам нужна такая же функциональность, вы могли бы использовать ключевое слово inline:
inline int max(int a, int b)
{
return (a > b) ? a : b;
}
Комментарии:
1. Зависит от того, что вы подразумеваете под «встроенным кодом». Это не означает, что код будет фактически «встроенным», если только компилятор не решит, что это полезно, и это произойдет, если код находится в заголовке или исходном коде.
Ответ №6:
Причина, по которой вам следует избегать этого вообще (для не встроенных функций), заключается в том, что несколько исходных файлов будут включать ваш заголовок, создавая ошибки компоновщика. Не имеет значения, используете ли вы pramga один раз или аналогичный трюк — дублирование будет обнаружено, если у вас есть более одной единицы компиляции (например, cpp-файлы), включающие один и тот же заголовок.
Комментарии:
1. Эту ошибку можно устранить, используя ключевое слово «inline». Это указывает компоновщику, что может существовать несколько версий функции, и он должен сохранить только одну.
2. даже при объявлении функции, если она включена несколько раз без prama один раз, это будет создавать ошибки компоновщика
3. @martin: вот почему я указываю для не встроенных функций.
Ответ №7:
Если вы хотите встроить функцию, она ДОЛЖНА быть в заголовке, иначе она не сможет быть встроена.
Если вы публикуете заголовок со своими библиотеками, и в заголовке есть какая-то реализация, вы можете быть уверены, что через несколько лет, если вы измените реализацию, и она не будет работать точно так же, как раньше, код некоторых пользователей сломается, поскольку они будут полагаться на реализацию, которую они видели в заголовке. Да, я знаю, что этого не следует делать, но многие люди ищут в заголовке реализацию и другое поведение, которые они могут использовать не по назначению, чтобы преодолеть какую-то проблему, с которой они сталкиваются.
Если вы планируете использовать шаблоны, то у вас нет другого выбора, кроме как поместить все это в заголовок. (это может быть необязательно, если ваш компилятор поддерживает шаблоны экспорта, но я знаю только 1 из них).
Комментарии:
1. Это не так. Современные компиляторы могут встроить код из других единиц перевода.
Ответ №8:
Нормально иметь реализацию в заголовке. Это зависит от того, что вам нужно. Если вы разделите определение в другой файл, компилятор создаст символы с внешней привязкой, если вы не хотите, чтобы вы могли определять функции внутри самого заголовка. Но вы бы потратили впустую некоторый объем памяти для сегмента кода. Если вы включите этот файл заголовка в два разных файла, то сегмент кодов обоих файлов будет иметь это определение функции.
Если в другом заголовочном файле будет функция с похожим именем, то это будет проблемой. Тогда вам придется использовать встроенный.
Комментарии:
1. Это приведет к потере места в нескольких объектных файлах. Но компоновщик удалит все вхождения, кроме одного, когда приложение / dll будет связано (если вы не объявите функцию как статическую).