2009-08-06 7 views
9

Я прочитал все, что мог найти на эту тему, включая пару очень полезных обсуждений на этом сайте, руководство по кодированию НАСА и рекомендации Google C++. Я даже купил рекомендованную здесь «физическую книгу на C++» (извините, забыл название) и получил некоторые полезные идеи. Большинство источников, похоже, согласны: файлы заголовков должны быть автономными, т. Е. Включать в себя то, что им нужно, чтобы файл cpp мог включать заголовок без включения каких-либо других и компилировать его. Я также высказываю мысль о том, чтобы объявить, а не включать, когда это возможно.Политика заголовка C++ для больших проектов (сокращение)

Это говорит, как о том, если foo.cpp включает bar.h и qux.h, но оказывается, что bar.h сама включает qux.h? Должно ли foo.cpp избегать включения qux.h? Pro: очищает foo.cpp (меньше «шум»). Con: если кто-то изменит bar.h, чтобы больше не включать qux.h, foo.cpp таинственно начинает сбой компиляции. Также становится очевидным, что зависимость между foo.cpp и qux.h не очевидна.

Если ваш ответ «cpp-файл должен содержать # заголовок каждого заголовка, который ему нужен», его логический вывод будет означать, что почти каждый файл cpp должен иметь значение #include <string>, <cstddef> и т. Д., Так как большинство кода в конечном итоге будут использовать эти и если вы не должны полагаться на какой-то другой заголовок, включая их, ваш cpp должен включать их явно. Это похоже на «шум» в файлах cpp.

Мысли?

Предыдущие обсуждения:

What are some techniques for limiting compilation dependencies in C++ projects?

Your preferred C/C++ header policy for big projects?

How do I automate finding unused #include directives?

ETA: Вдохновленный предыдущих дискуссий на здесь, я написал скрипт на Perl, чтобы последовательно закомментировать каждый 'включают' и «использование», затем попытайтесь перекомпилировать исходный файл, чтобы выяснить, что не нужно. Я также выяснил, как интегрировать его с VS 2005, поэтому вы можете дважды щелкнуть, чтобы перейти к «неиспользованному». Если кто-нибудь захочет, дайте мне знать ... сейчас очень экспериментально.

+0

Я предполагаю, что это должна быть вики сообщества, поскольку она, возможно, субъективна. – GManNickG

+0

Если вы помните, какое настоящее имя книги «физический дизайн на С ++», пожалуйста, отредактируйте вопрос - я бы хотел его проверить (если у меня еще нет). –

+0

Книга может быть «Дизайн программного обеспечения большого масштаба C++» Джона Лакоса: http://www.amazon.com/Large-Scale-Software-Addison-Wesley-Professional-Computing/dp/0201633620 –

ответ

8

Если ваш ответ «ПГКС файл должен #include каждый заголовок это необходимо», доведенный до логического конца, что будет означать, что почти каждый файл CPP должен #include <string>, <cstddef> и т.д., так как большая часть кода будет в конечном итоге с помощью тех, и если вы не должны полагаться на какой-то другой заголовок, включая их, ваш cpp должен включать их явно.

Yup. Я предпочитаю это.

Если «шум» слишком велик, вполне нормально иметь «глобальный» файл include, который содержит обычный общий набор включает (например, stdafx.h в большинстве программ Windows) и включает, что на начало каждого файла .cpp (что также помогает с предварительно скомпилированными заголовками).

+0

На самом деле, вещь stdafx - это один образец, с которого я пытаюсь убежать. Одна из проблем заключается в том, что наш код должен быть кросс-платформой, и, насколько я вижу, pch не работают одинаково в VC++ и gcc. Кроме того, мы закончили с «n квадратами» включили сложность, поскольку все закончилось, включая все, что привело к длительному времени компиляции. Думаю, мы могли бы включить STL в общее место, но это означает, что каждый блок компиляции получает их, хочет он или нет. Я также заметил, что заголовки STL включают друг друга, поэтому, если вы включите (например, «строку»), вам может не понадобиться «память» (просто пример). – 2009-08-07 16:35:10

+2

Я могу понять, почему вы не хотите использовать шаблон stdafx.h, но он может работать, и это шаблон, который я не считаю «плохой формой». Кроме того, я бы не стал выступать за то, чтобы бросить все, а кухня погрузилась в заголовок stdafx - только заголовки, которые ** действительно ** используются почти везде. Файл .cpp может содержать другие заголовки, которые менее часто нужны после файла stdaf.h.И я не думаю, что буду беспокоиться о множественном включении ненужных заголовков - они не должны слишком сильно влиять на время сборки (последующее включение файла не будет обработано). –

2

Я думаю, что вы все равно должны включать оба файла. Это помогает поддерживать код.

// Foo.cpp 

#include <Library1> 
#include <Library2> 

Я могу прочитать это и легко увидеть, какие библиотеки он использует.Если Library2 используется Library1, и он был преобразован в это:

// Foo.cpp 

#include <Library2> 

Но я до сих пор видел Library1 код, я мог бы быть немного запутался. Нетрудно догадаться, что какая-то другая библиотека должна включать его, но это все-таки мыслительный процесс, который должен произойти.

Будучи явным средством, я не должен угадывать, даже для дополнительной микросекунды компиляции.

+0

Вы бы также распространили это на библиотеки STL? например если файл cpp использует «string», вы хотите увидеть include? В каждом файле, который использует «строку»? Я определенно могу видеть ваши аргументы в отношении заголовков пользователей, но, глядя на мой код, большинство исходных файлов должны включать в себя группу STL. – 2009-08-07 16:37:18

+2

Подождите, что еще вы бы с ними сделали? Если ваш класс использует строки в заголовке, он пойдет туда, но если бы это была только реализация, которая подала в суд на него, это пошло бы в cpp. Каждый cpp * должен * включать заголовок, который ему нужен. – GManNickG

0

Каждый заголовочный файл содержит определения, необходимые для использования службы определенного типа (определение класса, некоторые макросы и т. Д.). Если вы напрямую используете службы, открытые через файл заголовка, включите этот заголовочный файл, даже если в него также входит другой файл заголовка. С другой стороны, если вы используете эти услуги только косвенно, только в контексте других сервисов заголовочного файла, не включайте файл заголовка напрямую.

По-моему, это не имеет большого значения в любом случае. Второе включение файла заголовка не полностью анализируется; он в основном прокомментирован для всех, но в первый раз он включен. По мере того, как все меняется, и вы узнаете, что используете заголовочный файл, в который вы не были напрямую включены, добавить его не стоит.

2

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

Компромисс может заключаться в том, что если что-то в bar.h содержит определение класса (или объявление функции), которое обязательно требует определения другого класса от qux.h, тогда можно принять/обязать bar.h/обязательное для включения qux.h, а пользователю API в bar.h не нужно беспокоиться, включая и то, и другое.

Итак, предположим, что клиент хочет думать о себе как о «действительно», только заинтересованном в API-интерфейсе, но также должен использовать qux, потому что он вызывает функцию бара, которая принимает или возвращает объект qux по значению. Тогда можно просто забыть, что у qux есть свой собственный заголовок и представить, что это один большой API-интерфейс API, определенный в bar.h, причем qux просто является частью этого API.

Это означает, что вы не можете рассчитывать, например, на поиск файлов cpp для упоминаний qux.h, чтобы найти всех клиентов qux. Но я никогда не полагался на это в любом случае, так как в целом слишком легко случайно пропустить явным образом, включая зависимость, которая уже косвенно включена, и никогда не осознавать, что вы не указали все соответствующие заголовки. Поэтому вы, вероятно, ни в коем случае не должны предполагать, что списки заголовков полны, но вместо этого используйте doxygen (или gcc -M или что-то еще), чтобы получить полный список зависимостей и выполнить поиск.

+0

Спасибо ... это хороший момент. Rampant включает * может * действительно быть запахом кода, признаком чрезмерной связи. Я обязательно посмотрю на это. – 2009-08-07 16:36:07

1

, если foo.cpp содержит bar.h и qux.h, но оказывается, что сам bar.h включает qux.h? Должен ли foo.cpp избегать включения qux.h?

Если foo.cpp использует что-либо из qux.h непосредственно, то он должен включать сам этот заголовок. В противном случае, поскольку bar.h нуждается в qux.h, я бы положился на bar.h, включая все, что ему нужно.

0

Один из способов подумать о том, использует ли foo.cpp напрямую qux.h, подумать о том, что произойдет, если foo.cpp больше не нужно включать bar.h. Если foo.cpp по-прежнему должен включать qux.h, то он должен быть явно указан. Если от qux.h больше ничего не потребуется, вероятно, нет необходимости включать его явно.

0

Здесь применяется правило «никогда не оптимизируйте без профилирования».

(Этот ответ смещен в сторону «производительности» над «беспорядком», беспорядок может вообще быть очищены по желанию, по общему признанию, это становится все труднее позже, но влияет на производительность вы на регулярной основе.)

Try в обоих случаях, посмотрите, есть ли какое-либо значительное улучшение скорости для вашего компилятора, и только потом подумайте об удалении «дубликатов» заголовков, потому что, как указывают другие ответы, существует долгосрочный штраф за техническую поддержку, который вы делаете, удаляя дубликаты.

Подумайте о том, чтобы получить что-то вроде Sysinternals FileMon, чтобы узнать, действительно ли вы создаете хиты файловой системы для всех этих дубликатов (большинство компиляторов не будут, с правильными защитниками заголовков).

Наш опыт здесь указывает на то, что активный поиск заголовков, которые полностью неиспользуемый (или может быть, с соответствующими декларациями вперед) и удалить их гораздо более достойным времени и усилий, чем выяснить, заголовочный цепи для возможных дубликатов. И хороший линт (splint, PC-Lint и т. Д.) Может помочь вам в этом определении.

Еще более достойным нашего времени и усилий было выяснить, как заставить наш компилятор обрабатывать несколько единиц компиляции за выполнение (почти линейное ускорение для нас, вплоть до точки - в компиляции преобладало начало компиляции) ,

После этого вы можете рассмотреть безумие «одиночного большого CPP». Это может быть весьма впечатляюще.

Смежные вопросы