Подтвердить что ты не робот

Существует ли стандартное соглашение #include для С++?

Это довольно простой вопрос, но это тот, который несколько время меня беспокоил.

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

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

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

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

4b9b3361

Ответ 1

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

bar.h:

class Foo;

class Bar
{
    Foo * m_foo;
};

Bar.cpp:

#include "Foo.h"
#include "Bar.h"

Ответ 2

Некоторые рекомендации:

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

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

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

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

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

Ответ 3

Ознакомьтесь с крупномасштабным дизайном программного обеспечения для разработки на С++ от John Lakos. Вот что я следую (написано в качестве примера):

Интерфейс

// 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

Ответ 4

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

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

class BogoFactory;

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

a.h
#include "b.h"

b.h
#include "c.h"

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

Ответ 5

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

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

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

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

Ответ 6

Основываясь на том, что сказал antti.huima:

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

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

Ответ 7

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