У меня есть эта сумасшедшая идея объединить заголовки предварительного объявления и файлы фактического объявления в один, выполнив некоторые трюки с макросами. Чтобы обеспечить некоторый контекст, ежедневная политика, которую я придерживаюсь для предварительных объявлений, выглядит следующим образом:
- Каждый файл заголовка имеет свое дополнение "_fwd.hpp", которое содержит предварительные объявления всех объектов, объявляемых в будущем. заголовочный файл.
- Я включаю заголовок прямого объявления, где достаточно прямого объявления фактической вещи
- Я включаю заголовок обычного объявления в основном в файлах .cpp и только тогда, когда требуется фактическая информация о реализации(вещи, требующие размера реализации, наследования и т. д.)
Но наличие отдельного заголовка _fwd.hpp для каждого заголовка загрязняет проект, и его сложно поддерживать. Итак, я пришел к следующей идее объединить предварительную декларацию и фактическую декларацию в один файл, а затем включить их в соответствии с числом включений. Я придумываю эту первоначальную идею двойного включения заголовка;
foo.hpp
#if !defined(FOO_FWD_H)
#define FOO_FWD_H
// Forward declarations goes here
struct foo;
#else // has forward_declaration, include actual if not included yet
#if !defined(FOO_H)
#define FOO_H
struct foo{
foo(){/*....*/}
};
// Normal declarations goes here
#endif // FOO_H
#endif // FOO_FWD_H
Если я включу "foo.hpp" один раз, я получу предварительное объявление foo, ноесли я включу его во второй раз в модуль перевода, я получу предварительное и фактическое объявление foo, что мне вполне подходит. (поскольку я все равно делаю то же самое, включите fwdecl в заголовок, актуально в cpp).
Таким образом, при использовании описанного выше варианта использования это выглядит так:
bar.hpp
#pragma once
#include "foo.hpp" // forward declaration
struct bar{
bar(const foo& f);
};
бар. cpp
#include "bar.hpp" // bar + 1st inclusion of foo.hpp
#include "foo.hpp" // included 2nd time, actual implementation enabled
bar::bar(const foo& f){
f.rab(); // requires actual implementation
}
Но, как вы можете себе представить, у этого подхода есть проблемы. Самая большая проблема заключается в том, что если foo.hpp, включенный в другой заголовок, tar.hpp и tar.hpp включен в bar.hpp, это приводит к тому, что фактическая реализация подвергается воздействию bar.hpp, что наносит ущерб цели. Кроме того, когда фактическая реализация foo.hpp требуется в bar.hpp, ее нужно включать дважды, что выглядит странно (у линтеров и инструментов, таких как iwyu, могут быть проблемы с этим).
Так что вопрос кипитвплоть до этого, можем ли мы на самом деле сделать эту работу таким образом, чтобы
- , включая заголовки, которые с помощью этой идиомы не мешали состоянию включения других заголовков
- , устраняя необходимостьдвойное включение при необходимости фактической реализации
Заранее спасибо.
ОБНОВЛЕНИЕ: (30/10/19 22:57 GMT + 2)
Улучшенная версия идиомы, основанная на ответе @IanAbbott:
Попробуйте вживую: repl.it
foo.hpp (наш единственный заголовок реализации fwdecl & decl)
// (mgilor): we got ourselves quite a lot boilerplate code,
// maybe x-macro concept help us to move away boilerplate to
// a separate file?
#if defined(FOO_FWD_ONLY)
#undef FOO_FWD_HPP // prevent accidental implementation inclusion on other headers
#endif
#if defined(FOO_FWD_ONLY) && !defined(FOO_FWD_HPP)
#define FOO_FWD_HPP
// forward declarations go here
struct foo;
#elif !defined(FOO_FWD_ONLY)
// includer wants the full monty
#if !defined(FOO_HPP)
#define FOO_HPP
// actual declarations go here
struct foo{
foo(){/*....*/}
void do_things(){}
};
#endif // FOO_HPP
#endif // FOO_FWD_HPP
// undef the macro, so future includes does not get affected
#undef FOO_FWD_ONLY
tar.hpp (fwdecl только потребитель foo)
#pragma once
#define FOO_FWD_ONLY
#include "foo.hpp" // this header needs forward declaration
#ifdef FOO_FWD_HPP
#pragma message ( __FILE__ " has forward declaration of foo")
#endif
#ifdef FOO_HPP
#pragma message ( __FILE__ " has full declaration of foo")
#endif
struct tar{
tar(foo & f){ }
};
bar. hpp (fwdecl только потребитель foo, также использует tar.hpp)
#pragma once
#include "tar.hpp" // tar consumed foo fwdecl-only
#define FOO_FWD_ONLY
#include "foo.hpp" // bar needs fwdecl-only
#ifdef FOO_FWD_HPP
#pragma message ( __FILE__ " has forward declaration of foo")
#endif
#ifdef FOO_HPP
#pragma message ( __FILE__ " has full declaration of foo")
#endif
struct bar{
bar(foo & f);
};
bar.cpp (полный потребитель declbar & foo)
#include "bar.hpp"
#include "foo.hpp" // second inclusion, should enable full definition
#ifdef FOO_FWD_HPP
#pragma message ( __FILE__ " has forward declaration of foo")
#endif
#ifdef FOO_HPP
#pragma message ( __FILE__ " has full declaration of foo")
#endif
bar::bar(foo& ref){
ref.do_things();
}
baz.hpp (без зависимостей)
#pragma once
struct baz{
void do_baz();
};
baz.cpp (полный decl-потребитель foo& baz)
#include "baz.hpp"
#include "foo.hpp" // no prior include of foo, but since FOO_FWD_ONLY is not defined
// baz.cpp will get full declaration.
#ifdef FOO_FWD_HPP
#pragma message ( __FILE__ " has forward declaration of foo")
#endif
#ifdef FOO_HPP
#pragma message ( __FILE__ " has full declaration of foo")
#endif
void baz::do_baz(){
foo f;
f.do_things(); // completely fine.
}
main.cpp (приложение-потребитель)
// consuming application
#include "tar.hpp"
#include "bar.hpp"
#include "foo.hpp" // already has previous foo fwdecl, so second inclusion will enable full declaration.
// (also FOO_FWD_ONLY is not defined, so first inclusion would enable it too)
#include "baz.hpp"
int main(void){
foo f;
tar t(f);
bar b(f);
baz bz;
}
Вывод при компиляции:
tar.hpp:7:13: warning: tar.hpp has forward declaration of foo
bar.hpp:8:13: warning: bar.hpp has forward declaration of foo
bar.cpp:6:13: warning: bar.cpp has forward declaration of foo
bar.cpp:9:13: warning: bar.cpp has full declaration of foo
baz.cpp:9:13: warning: baz.cpp has full declaration of foo
tar.hpp:7:13: warning: tar.hpp has forward declaration of foo
bar.hpp:8:13: warning: bar.hpp has forward declaration of foo