Требует ли определенного порядка для #include в C++ признак плохого дизайна библиотеки / заголовка?
Я использовал несколько очень масштабных систем и никогда не видел требуемого заказа, но недавно столкнулся с ним. Есть ли в библиотеке STL или STD или даже в Boost какие-либо случаи, когда определенные включения должны приходить в определенном порядке?
13 ответов
Есть ли в библиотеке STL или STD или даже в Boost какие-либо случаи, когда определенные включения должны приходить в определенном порядке?
Для стандарта ответ решительно, нет. Я полагаю, что то же самое верно для Boost, хотя я не смотрел это.
Из стандарта С:
Стандартные заголовки могут быть включены в любом порядке; каждый из них может быть включен более одного раза в данную область, без эффекта отличающегося от включения только один раз, за исключением того, что эффект включения
<assert.h>
зависит от определенияNDEBUG
(см. 7.2).
Стандарт C++ имеет аналогичную формулировку.
Я предпочитаю, чтобы заголовки включали свои собственные зависимости, но я работал с людьми, которые считают это "расточительным". По моему мнению, отсутствие заголовков, включающих их зависимости, - бесполезная ранняя оптимизация.
Это определенно звучит как плохой дизайн. Если каким-то образом требуется определенный порядок, библиотека должна предоставить один заголовок, который включает другие заголовки в правильном порядке.
Что касается буста и STL, я почти уверен, что еще не сталкивался с такой ситуацией.
Необходимость указать включает в конкретном порядке почти всегда указывается проблема дизайна. Одним из способов уменьшить вероятность непреднамеренного выполнения этого является использование практики включения заголовочного файла класса в качестве первого #include в файле реализации.
// A.cpp
#include "A.h"
#include "boost/shared_ptr.hpp"
#include <vector>
class A {
// ...
};
Таким образом, если, например, Ah использует вектор без правильного #include, A.cpp не будет компилироваться.
Я не могу вспомнить, где я поднял это; это могло быть из "Large Scale C++ Design" Лакоса (отличная книга, которая действительно могла бы использовать обновление).
Общепринятым методом является включение заголовка совместимости на уровне проекта (скажем, compat.h) в качестве первого заголовка любых исходных файлов.c /.cpp, который определяет набор необходимых макросов, таких как __STDC_LIMIT_MACROS, __REENTRANT и другие макросы всего проекта, чтобы повлиять на последующее поведение стандартных заголовков.
Впервые я увидел это использование компетентным программистом для внутренней библиотеки. Позже я увидел, что проект 'git' (печально известные dvcs) также использовал эту технику.
Это "плохая вещь". Лучший способ был упомянут; но я уточню.
//a.h #ifndef _A_H_ #define _A_H_ //... code ... #endif // ----------------- //b.h #ifndef _B_H_ #define _B_H_ #include a.h //... code ... #endif // ----------------- //main.cpp Try 1 #include "b.h" //<- okay! b includes a, then does b // ----------------- //main.cpp Try 2 #include "a.h" //<- includes a #include "b.h" //<- okay! b includes a, but skips redefining it, then does b // ----------------- //main.cpp Try 3 #include "b.h" //<- b includes a, then does b #include "a.h" //<- okay! a skips redefining itself! // ----------------- //main.cpp Try 4 #include "a.h" //<- fail! b is not included anywhere =(
Есть ли в библиотеке STL или STD или даже в Boost какие-либо случаи, когда определенные включения должны приходить в определенном порядке?
Я никогда не сталкивался с этим, и если это так, то авторы должны быть уведомлены как можно скорее. И да, это очень плохой дизайн.
Если функции и / или классы, содержащиеся в заголовке (скажем, Ah), зависят от функций и / или классов, определенных в другом заголовке (скажем, Bh), я предпочитаю включать последний в первый, а не заставлять пользователей первого один, чтобы включить оба в определенном порядке.
Да:
// A.h
#pragma once
// or the #ifndef trick
#include "B.h"
// A.cpp
#include "A.h"
Нет:
// A.h
#pragma once
// or the #ifndef trick
//#include "B.h"
// A.cpp
#include "B.h"
#include "A.h"
Для меня это плохой дизайн, который, к сожалению, случается в Win32 API с включенным socket/socket2, если я правильно помню. Результатом является то, что ошибка в порядке включения вызовет набор ошибок, которые просто происходят из ниоткуда, и их может быть сложно отладить в тех случаях, когда зависимость меняет определения, но код по-прежнему компилируется.
В любом другом случае вы все равно столкнетесь с неприятностями. Если вы не включаете заголовок xh, потому что yh уже включает его, то ваш код зависит от зависимости yh от xh. Если позднее yh подвергается рефакторингу и больше не требует yh, удаление включения нарушит вашу базу кода. Это признак связывания (даже если не на уровне класса): изменения в одной части кодовой базы должны распространяться и распространяться на другие части кода.
Возможно, это признак того, что вы используете MFC, что, в свою очередь, может указывать на плохой дизайн (шутка... или это?)
(По крайней мере, в последний раз, когда я смотрел на MFC, было очень требовательно <windows.h>
)
Мне нравится включать заголовки в алфавитном порядке - позволяет легко увидеть, что я уже сделал.
Если библиотека не работает, потому что она в неправильном порядке, то она сломана и должна быть исправлена, чтобы быть независимой от порядка.
Вы должны использовать include guard и forward-декларации, таким образом у вас не должно быть особых проблем с порядком включения заголовков.
Иногда все еще требуется, чтобы заголовок был первым или последним, не знаю почему.
(Например: в исходном SDK)
Да, требование определенного порядка для включений в C++ является признаком плохого дизайна библиотеки / заголовка.
Хотя для прямого использования может потребоваться включение более одного файла, чтобы полностью использовать класс. Смотрите пример ниже:
// Ах
class B; // forward declaration
class A
{
void doStuff(const B& b);
};
// main.cpp
#include <A.h>
#include <B.h>
int main()
{
A a;
B b;
a.doStuff(b);
}
Не в моих знаниях. Это довольно плохая практика. Я недавно столкнулся с этим с заголовком Windows и некоторым странным интерфейсом в моем коде.