Страницы

Поиск по вопросам

воскресенье, 1 декабря 2019 г.

Include в заголовочных файлах

#cpp


Заметил, что в разных проектах C++ программисты по разному используют директиву #include
1) В первом случае include по максимуму прописывают в h-файлах, но уже не пишут в
cpp-файлах. Т.е. делаешь включение одного заголовочного файла, а он уже тянет все инклуды
в себе.
2) Во втором случае наоборот, в h-файлах почти нет include, но все эти include спишут
в cpp-файлах. При этом, если есть некие общие классы, то могут сделать так:


В начале заголовочного файла ставят пустое объявление класса: class myClass;. Но
этот include будет в cpp-файле.


В заголовочном файле вообще нет объявления внешних типов данных. Но всё работает
если в cpp-файле подключать заголовочные файлы с описанием классов перед подключением
зависимых заголовочных файлов, т.е. важен порядок


Вопросы у меня такие: 

Есть ли формальное наименование этих стилей компановки исходного кода? 
Как делать правильно, а как делать нельзя?
Хочется изучить эту тему от и до. С чего начать?
    


Ответы

Ответ 1



По моему опыту в большинстве случаев работает такой простой критерий: "минимальная" программа с любым из Ваших инклюд-файлов должна собираться. Например: // t.cpp #include "myincl1.h" int main () { return 0; } это весь текст g++ t.cpp Компиляция должна успешно пройти. А в "myincl1.h" при этом желательно включать минимальное количество других .h файлов (как системных, так и собственных). И конечно, не забывайте писать #ifndef _MYINCL1_H #define _MYINCL1_H .... #endif // _MYINCL1_H в начале и конце своих .h. Собственно, это требование к правильно написанному заголовочному файлу. Естественно, не должно быть никаких зависимостей от порядка включения файлов. Иногда для сокращения писанины удобно написать два-три "обобщающих" .h-файла, которые включают большинство собственных и системных .h, нужных для конкретной программы (используются во многих ее модулях).

Ответ 2



Хочется изучить эту тему от и до. С чего начать? Книжек про это не встречал Есть ли формальное наименование этих стилей компановки исходного кода? Не знаю Как делать правильно, а как делать нельзя? Вот мои рассуждения: Думаю следует различать 2 ситуации: Заголовочный файл содержит какие-то функции / классы (объявление + реализацию), которые могут быть использованы в разных проектах в самых разных местах. Такой файл должен включать в себя все заголовочные файлы, которые ему могут понадобится. По принципу "подключил и забыл", чтобы не нужно было самому подключать какие-то дополнительные заголовочные файлы. Заголовочный файл (.h) содержит описание класса / функций, а файл (.cpp) содержит их реализацию. В таком случае или в .h файл включатся только те инклуды, которые содержат объекты, присутствующие в описании классов / функций (например std::vector), или не включается ничего, а всё-всё-всё включается в .cpp файл. Первый способ позволяет не плясать с порядком инклудов в .cpp файле, второй не делать лишние инклуды, но требует правильного порядка подключения. Правило подключения имхо очень простое, его не сложно запомнить и не сложно ему следовать: в файле MyClass.cpp #include (1) сначала подключаются файлы стандартной библиотеки #include (2) затем подключатся файлы 3rd-party библиотек, объекты и функции которых Вы используете в своём коде #include (3) свои внутренние заголовочные файлы #include (3) свой заголовочный файл, реализацию для которого мы пишем Пустое объявление myClass имхо хорошее решение (следуя принципу чем меньше инклудов и чем они "локальнее", тем лучше), но есть подводный камень (см. для чего нужен boost::checked_delete). Обычно пустые объявления используют для указателей, нужно проследить чтобы эти указатели не удалялись. И не забывать про существование precompiled headers

Ответ 3



Трудно однозначно ответить на этот вопрос. Скорее, тут дело вкуса или даже масштаба задачи: если это какой-нибудь опенсорсный проект, то тогда для удобства других целесообразно поместить все инклуды в заголовочный файл, если же вы решаете задачу для себя( проводите исследование, к примеру ), то о таком подходе разбиения кода можно даже не думать особо - тут достаточно и одного файла, где будет расположено все, все, все. А в целом, логическое разбиение кода - хорошая привычка.

Ответ 4



Ну в случае "всеобъемлющих" header-файлов - может получиться циклическая зависимость. И вполне вероятно если не придерживаться четко какому-либо "стилю" , то группа разработчиков вполне может достичь такого эффекта. В результате "сильно связанную" h-ку нельзя использовать в зависящем проекте, где и без нее своих объявлений хватает. Поэтому мой вариант в идеале писать так в h-файлах для поддержания порядка: #ifndef SOME_NEED_H #error Пожалуйста подключите SOME_NEED_H #endif Так загадки в порядке нет. Ну а если лень - тогда действительно pch (аккуратно составленный) в помощь. Я за минимализм в h-ках и за some_header_tmpl.h (где есть все необходимое) для cpp.

Ответ 5



"минимальная" программа с любым из Ваших инклюд-файлов должна собираться. Мне одному кажется, что это очень странный критерий? Хотелось бы предостеречь автора вопроса, от столь интересных критериев. По-хорошему в хидере должно быть как можно меньше #include. Делается это как правило для того, чтобы компилироваться проект быстро [и только потом уже, чтобы собирать нормальные библиотеки, без лишних и никому не нужных зависимостей].

Ответ 6



Если Вы хотите, чтобы с вашими заголовочными файлами было как можно меньше неприятных сюрпризов - в модуле должны быть указаны все необходимые для него заголовочные файлы. Не следует полагаться на то, что один .h включит в себя другой. Если Вы собираетесь всю жизнь работать с одним компилятором, причём одной версии, разницы Вы можете не увидеть. Но как правило, так не получается. И хорошо написанная программа на C++ должна подразумевать, что её будут собирать разными компиляторами, а иногда и на разных операционных системах. И если вы привыкли к "минимализму", который тут некоторые советуют, вы обнаружите, что в одних условиях Ваша программа собирается, в других нет, причём зависит это в буквальном смысле от фазы Луны. Дело в том, что в С++ (в отличие, допустим от Java, Oberon и даже Object Pascal) отсутствуют языковые средства поддержки модульности. Есть только раздельная компиляция. А для обеспечения модульности приходится применять костыли - #include, namespace и др. И неправильное их применение чревато тяжёлыми ошибками. Чтобы не было циклических зависимостей, а также, чтобы модуль с большим количеством включённых заголовочных файлов не собирался слишком долго, существуют стражи компиляции (стражи включения, include guards) - см. статью в Википедии. И в грамотно написанном заголовочном файле их применение обязательно! К сожалению, некоторые сторонники "минимализма" здесь, боюсь, не подозревают об их наличии. Совет собирать ссылки на часто используемые заголовочники в один "метазаголовочник" тоже, как правило, не очень хорош. Поменяете Вы один файл, а пересобираться будет десяток, причём большей частью тех, которым он совсем не нужен. Прекомпиляция для больших проектов может быть хорошим решением, однако помните, что в разны компиляторах она может работать по-разному. Возможно, я написал много букв, но мой совет - это совет человека, который много лет пишет переносимые программы под разные ОС. И у меня была возможность убедиться, что копеечная экономия на времени сборки порой оборачивается тяжёлыми проблемами. Желаю удачи в написании грамотных программ на C++.

Ответ 7



Знаю, что тема давно устарела, но может кому-нибудь понадобится. Второй случай, когда мы покдлючаем минимальное количество заголовочных файлов в .h хорошо помогает при уменьшении времени компиляции проекта, если он достаточно большой. Например, такой подход активно используется в Qt.

Комментариев нет:

Отправить комментарий