Существует ли стандартное соглашение #include для C ++? - PullRequest
17 голосов
/ 27 марта 2009

Это довольно простой вопрос, но он меня долго мучил.

В моем проекте есть несколько файлов .cpp (реализация) и .hpp (определение).

Я считаю, что при добавлении дополнительных классов и дополнительных взаимозависимостей классов я должен #include другие заголовочные файлы. Через неделю или две я получаю директивы #include во многих местах. Позже я попытаюсь удалить некоторые из #includes и обнаружу, что все по-прежнему работает, потому что некоторый ДРУГОЙ включенный класс также #inclusive, что я только что удалил.

Есть ли простое и легкое правило для добавления в #include, которое вообще остановит этот ужасный беспорядок? Какова лучшая практика?

Например, я работал над проектами, в которых файл .cpp для реализации включает в себя ТОЛЬКО соответствующий файл Definition .hpp и ничего больше. Если есть какие-либо другие файлы .hpp, которые должны использоваться реализацией .cpp, на них все ссылается файл Definition .hpp.

Ответы [ 8 ]

18 голосов
/ 27 марта 2009

Некоторые лучшие практики:

  • Каждый файл .cpp или .C включает все необходимые заголовки и не зависит от заголовков, включая другие связанные заголовки
  • Каждый файл .hpp или .h включает в себя все свои зависимости и не зависит от включенных заголовков, включая другие связанные заголовки
  • Каждый заголовок обернут:

    #ifndef HEADER_XXX_INCLUDED
    #define HEADER_XXX_INCLUDED
    ...
    #endif /* HEADER_XXX_INCLUDED */
    
  • Заголовки не включают друг друга в циклы

  • Часто: существует один «заголовочный файл для всего проекта», например «config.h» или «.h», который всегда включается first в любой файл .cpp или .C. Как правило, это данные конфигурации платформы, константы всего проекта, макросы и т. Д.

Это не обязательно «лучшая практика», но правила, которым я обычно следую:

  • Заголовки для конкретного проекта включены как #include "..." и перед общесистемными заголовками, которые включены как #include <...>
  • Заголовки для конкретных проектов включены в в алфавитном порядке , чтобы гарантировать, что не существует случайного, скрытого требования, в каком порядке они включены. Поскольку каждый заголовок должен включать в себя свои зависимости и , заголовки должны быть защищены от многократного включения, вы должны иметь возможность включать их в любом порядке.
10 голосов
/ 28 марта 2009

Ознакомьтесь с крупномасштабным программным обеспечением C ++ Джона Лакоса. Вот что я следую (написано в качестве примера):

Интерфейс

// foo.h
// 1) standard include guards.  DO NOT prefix with underscores.
#ifndef PROJECT_FOO_H
#define PROJECT_FOO_H

// 2) include all dependencies necessary for compilation
#include <vector>

// 3) prefer forward declaration to #include
class Bar;
class Baz;
#include <iosfwd> // this STL way to forward-declare istream, ostream

class Foo { ... };
#endif

Осуществление

// foo.cxx
// 1) precompiled header, if your build environment supports it
#include "stdafx.h"

// 2) always include your own header file first
#include "foo.h"

// 3) include other project-local dependencies
#include "bar.h"
#include "baz.h"

// 4) include third-party dependencies
#include <mysql.h>
#include <dlfcn.h>
#include <boost/lexical_cast.hpp>
#include <iostream>

Предварительно скомпилированный заголовок

// stdafx.h
// 1) make this easy to disable, for testing
#ifdef USE_PCH

// 2) include all third-party dendencies.  Do not reference any project-local headers.
#include <mysql.h>
#include <dlfcn.h>
#include <boost/lexical_cast.hpp>
#include <iosfwd>
#include <iostream>
#include <vector>
#endif
7 голосов
/ 27 марта 2009

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

bar.h:

class Foo;

class Bar
{
    Foo * m_foo;
};

Bar.cpp:

#include "Foo.h"
#include "Bar.h"
5 голосов
/ 27 марта 2009

Используйте только минимальное количество необходимых включений. Бесполезное в том числе замедляет компиляцию.

Кроме того, вам не нужно включать заголовок, если вам просто нужен указатель на класс. В этом случае вы можете просто использовать предварительную декларацию, например:

class BogoFactory;

редактировать: просто чтобы прояснить. Когда я сказал минимальную сумму, я не имел в виду создание цепочек включающих в себя:

a.h
#include "b.h"

b.h
#include "c.h"

Если a.h требуется c.h, его необходимо включить в a.h для предотвращения проблем с обслуживанием.

3 голосов
/ 27 марта 2009

Существует несколько проблем с моделью #include, используемой в C / C ++, главная из которых заключается в том, что она не выражает фактический граф зависимостей. Вместо этого он просто объединяет группу определений в определенном порядке, часто приводя к тому, что определения приходят в другом порядке в каждом исходном файле.

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

Два соглашения (помимо уже упомянутых), которые могут помочь:

  • Один класс == один исходный файл + один заголовочный файл с постоянным именем. Класс A идет в A.cpp и A.h. Шаблоны кода и фрагменты кода хороши здесь, чтобы уменьшить объем ввода, необходимый для объявления каждого класса в отдельном файле.
  • Используйте Impl-шаблон, чтобы не отображать внутренние элементы в заголовочном файле. Шаблон impl означает помещение всех внутренних членов в структуру, определенную в файле .cpp, и просто наличие частного указателя с предварительным объявлением в классе. Это означает, что файл заголовка должен включать только те файлы заголовка, которые необходимы для его открытого интерфейса, а любые определения, необходимые для его внутренних членов, будут храниться в файле заголовка.
2 голосов
/ 27 марта 2009

Опираясь на то, что ант.хима сказал:

Допустим, у вас есть классы A, B и C. A зависит от (включает) B, а A и B зависят от C. Однажды вы обнаружите, что вам больше не нужно включать C в A, потому что B делает это для вас, и поэтому вы удалите это #include заявление.

Что произойдет, если в какой-то момент в будущем вы обновите B, чтобы больше не использовать C? Внезапно А сломался без веской причины.

1 голос
/ 28 марта 2009

В A.cpp всегда сначала включайте A.h, чтобы убедиться, что A.h не имеет дополнительных зависимостей. Включите все локальные (один и тот же модуль) файлы перед всеми файлами проекта перед всеми системными файлами, снова, чтобы убедиться, что ничего не зависит от предварительно включенных системных файлов. Используйте как можно больше предварительных деклараций. Используйте # indef / # define / # endif pattern Если заголовок включен в A.h, вам не нужно включать его в A.cpp. Любые другие заголовки, которые нужны A.cpp, должны быть явно включены, даже если они предоставлены другими файлами .h.

0 голосов
/ 28 марта 2009
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...