Включение заголовочных файлов в C/С++ более одного раза
Полезно ли включать заголовочный файл более одного раза в C или С++?
Если механизм никогда не используется, зачем компилятору когда-либо беспокоиться о том, чтобы включить файл дважды; если это действительно бесполезно, не было бы более удобно, если бы новые компиляторы удостоверились, что каждый заголовок включен только один раз?
Edit:
Я понимаю, что существуют стандартные способы делать такие вещи, как включить охранников и pragma once, но зачем вам нужно указывать даже это? Не должно ли поведение компилятора по умолчанию включать файлы только один раз?
Ответы
Ответ 1
Да, это полезно при создании кода с препроцессором или при выполнении трюков, таких как Boost.PP.
В качестве примера см. X Макросы. Основная идея заключается в том, что файл содержит тело макроса, а вы #define
аргументы, а затем #include
. Здесь надуманный пример:
macro.xpp
std::cout << MESSAGE;
#undef MESSAGE
file.cpp:
int main() {
# define MESSAGE "hello world"
# include "macro.xpp"
}
Это также позволяет использовать #if
и друзей в аргументах, что обычные макросы не могут сделать.
Ответ 2
Да, включая заголовок более одного раза, может быть полезно (хотя это довольно необычно). Канонический пример <assert.h>
, который определяет asssert
по-разному в зависимости от того, определена ли NDEBUG
или нет. Таким образом, имеет смысл включить его, затем иметь (обычно условное) определение NDEBUG, а затем включать его снова, с (по крайней мере потенциально) различными определениями assert
:
Макрос assert
переопределяется в соответствии с текущим состоянием NDEBUG
каждый раз, когда <assert.h>
включен 1.
Большинство заголовков, однако, приходят к некоторым болям, чтобы быть идемпотентными (т.е. иметь одинаковые эффекты независимо от того, как часто они включаются).
1 C99, §7.2/1.
Ответ 3
Типичный пример (непроверенный) - точка, заключающаяся в том, что он составляет список перечислений, поэтому они последовательно отображаются в enum
и в потоковом коде:
// states.h
X(Uninitialised)
X(Initialised)
X(Running)
X(Complete)
X(Shutdown)
// app.c++
#if defined(X)
# error X is already defined
#endif
enum States {
#define X(TAG) TAG,
#include "states.h"
#undef X
Extra_So_Trailing_Comma_Ignored
};
std::ostream& operator<<(std::ostream& os, const States& state)
{
#define X(TAG) if (state == TAG) return os << #TAG;
#include "states.h"
#undef X
return os << "<Invalid-State>";
}
Ответ 4
Да, было бы удобнее только один раз включить его, и именно поэтому вы используете #pragma один раз. в С++:)
Edit:
Примечание: #pragma один раз не переносится. Вы можете использовать
#ifndef FILENAME_H
#define FILENAME_H
в верхней части ваших файлов заголовков, если вы хотите, чтобы он был переносимым.
Ответ 5
Многократное включение может использоваться всякий раз, когда вам нужно какое-то "скучное" генерирование кода, которое вы не хотите поддерживать вручную, снова и снова.
Классическим примером может быть перечисление C/С++ и соответствующие строки, которые более или менее выглядят следующим образом:
// MYENUM_VALUES.H
MYENUMVALUE(a)
MYENUMVALUE(b)
MYENUMVALUE(c)
MYENUMVALUE(d)
// MYENUM.H
#define MYENUMVALUE(x) x,
enum MyEnum
{
#include "MYENUM_VALUES.H"
}
#undef MYENUMVALUE
// MYENUMTOSTRING.C
#define MYENUMVALUE(x) case x : return #x;
const char * MyEnumToString(MyEnum val)
{
switch (val)
{
#include "MYENUM_VALUES.H"
default return "unknown";
}
}
#undef MYENUMVALUE
Ответ 6
#ifndef _INC_HEADER_
#define _INC_HEADER_
//header code
#endif
Где HEADER
- имя заголовка
например. main_win.h
будет _INC_MAIN_WIN_