Я создаю библиотеку C ++ 11/14 только для заголовков, и я не уверен, как мне поступить #include
директивы между библиотечными файлами.
Должен ли я попытаться сгруппировать как можно больше #include
директивы, насколько это возможно, в заголовочном файле модуля, ориентированного на пользователя, или внутренние файлы должны включать файлы, которые им требуются (иногда повторяя те же самые включения)?
Подход А:
При таком подходе файл заголовка модуля включает в себя все необходимые зависимости, а затем включает в себя реализации. Заголовочные файлы реализаций не содержат ничего сами по себе.
// Library/Module/Module.hpp
// This file is intended to be included by the user in his projects.
#ifndef MODULE
#define MODULE
#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"#include "Library/Module/Impl/Class1.hpp"#include "Library/Module/Impl/Class2.hpp"
#endif MODULE
—
// Library/Module/Impl/SharedDependency.hpp
#ifndef SHARED_DEPENDENCY
#define SHARED_DEPENDENCY
inline void sharedFunc() { }
#endif
—
// Library/Module/Impl/Class1.hpp
#ifndef CLASS1
#define CLASS1
// No need to include "SharedDependency.hpp", as it will be included by
// the module header file. Same applies for <vector>.
struct Class1
{
std::vector<int> v;
Class1() { sharedFunc(); }
};
#endif
—
// Library/Module/Impl/Class2.hpp
#ifndef CLASS2
#define CLASS2
// No need to include "SharedDependency.hpp", as it will be included by
// the module header file. Same applies for <vector>.
struct Class2
{
std::vector<int> v;
Class2() { sharedFunc(); }
};
#endif
Подход Б:
При таком подходе файл заголовка модуля включает только заголовки реализации. Если заголовки реализации требуют дополнительных включений, они включают сами файлы (рекурсивно), иногда повторяя одно и то же включение.
// Library/Module/Module.hpp
// This file is intended to be included by the user in his projects.
#ifndef MODULE
#define MODULE
#include "Library/Module/Impl/Class1.hpp"#include "Library/Module/Impl/Class2.hpp"
#endif MODULE
—
// Library/Module/Impl/SharedDependency.hpp
#ifndef SHARED_DEPENDENCY
#define SHARED_DEPENDENCY
inline void sharedFunc() { }
#endif
—
// Library/Module/Impl/Class1.hpp
#ifndef CLASS1
#define CLASS1
#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"
struct Class1
{
std::vector<int> v;
Class1() { sharedFunc(); }
};
#endif
—
// Library/Module/Impl/Class2.hpp
#ifndef CLASS2
#define CLASS2
#include <vector>
#include "Library/Module/Impl/SharedDependency.hpp"
struct Class2
{
std::vector<int> v;
Class2() { sharedFunc(); }
};
#endif
Каков наилучший подход?
Я думаю, что интуитивно Подход А является лучшим, так как он избегает повторения тех же самых включений и проясняет, какие файлы должны быть включены перед другими файлами. Однако самый большой недостаток заключается в том, что подсветка синтаксиса перестает работать в моей IDE (QT-Creator), в файлах реализации без директив include.
РЕДАКТИРОВАТЬ:
Этот вопрос был признан закрытым по причине «основанный на мнении». Я не согласен, потому что в большом проекте с заголовками, таком как моя библиотека, включая файлы, может потребоваться много времени для компиляции. Следовательно, подход A может быть быстрее, чем подход B или наоборот.
Подход Б на самом деле является лучшим подходом, поскольку многократное включение одного и того же заголовка не приводит к заметному увеличению времени компиляции, но выгодно по следующим причинам:
Современные IDE могут использовать libclang
или запатентованные решения для разбора #include
директивы и предоставляют функции подсветки синтаксиса и автозаполнения с учетом кода.
Как уже упоминалось в TemplateRex, становится намного проще проверить нормальный процесс сборки. Например, CMake предоставляет макросы, которые автоматически генерируют тест для каждого заголовка.
Как уже упоминал Альф, хорошей практикой является включение в каждый файл всех заголовков, от которых он зависит — пользователи библиотеки могут затем «выбирать файлы заголовков», которые им требуются, вместо неожиданного принудительного включения родительского заголовка вручную.