是否可以将前向声明和常规声明合并到一个文件中,然后像分开一样使用它?

Is it possible to merge forward declaration and regular declaration into a single file, then use it as if they're separated?

本文关键字:声明 后像 然后 一样 文件 常规 合并 一个 是否      更新时间:2023-10-16

我有一个疯狂的想法,通过执行一些宏技巧将前向声明头和实际声明文件合并为一个。为了提供一些上下文,我对远期申报遵循的日常政策如下;

  • 每个头文件都有其"_fwd.hpp"补码,它包含该头文件中所有可正向声明实体的正向声明
  • 我包括正向声明头,其中实际事物的正向声明就足够了
  • 我主要在.cpp文件中包含正则声明头,并且只有在需要实际实现信息时(需要实现大小、继承等(

但是为每个标头单独设置_fwd.hpp标头会污染项目,而且有点难以维护。因此,我提出了以下想法,将正向声明和实际声明合并到一个文件中,然后根据include计数启用它们。我提出了这个头的双重包含的初步想法;

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中包含actual(。

所以,当放到上面描述的用例中时,它是这样的;

bar.hpp

#pragma once
#include "foo.hpp" // forward declaration
struct bar{
bar(const foo& f);
};

bar.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.hp中,从而破坏目的。此外,当在bar.hpp中需要foo.hpp的实际实现时,它必须包含两次,这看起来很奇怪(linters和iwyu等工具可能对此有问题(。

因此,问题归结为,我们真的能以这样的方式做到这一点吗;

  • 包含使用此习惯用法不会干扰其他标头的包含状态的标头
  • 在需要实际实施时消除双重包含的需要

提前感谢。

更新:(格林尼治标准时间19年10月30日下午10:57+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(仅限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(bar&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(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 

这里有一个建议(可能不是最好的(。它涉及到include文件的includer设置一个宏,以指示它只需要该include文件中的正向声明。如果未定义宏,它将从包含文件中获取所有内容。为了避免头文件在之后忘记取消定义特殊宏的问题,可以让包含的文件负责取消定义。

它是这样的:

foo.hpp

#if !defined(FOO_FWD_HPP)
#define FOO_FWD_HPP
// forward declarations go here
struct foo;
#endif // FOO_FWD_HPP
#if !defined(FOO_FWD_ONLY)
// includer wants the full monty
#if !defined(FOO_HPP)
#define FOO_HPP
// normal declarations go here
struct foo{
foo(){/*....*/}
};
#endif // FOO_HPP
#endif // FOO_FWD_ONLY
#undef FOO_FWD_ONLY

bar.hpp

#pragma once
// only need forward declarations from foo.hpp
#define FOO_FWD_ONLY
#include "foo.hpp"
struct bar {
bar(const foo& f);
};

bar.cpp

#include "bar.hpp"
#include "foo.hpp"
bar::bar(const foo& f){
f.rab(); // requires actual implementation
}

主要的好处是减少了正在编译的代码量。它并没有解决意外暴露的问题。例如,如果"bar.hpp"包含包含"foo.hpp"的其他文件,而没有首先定义FOO_FWD_ONLY宏,则"foo.hsp"中的完整定义将暴露给"bar.hsp"的其余部分。