在类声明/定义中包含标头

Including headers inside class declaration / definition

提问人:Luchian Grigore 提问时间:9/8/2011 更新时间:1/3/2022 访问量:3122

问:

我知道你可以做这样的事情:

def.h:

A();
int x;

class A
{
public:
#include "def.h"
}

.cpp

A::A()
{
    x = 0;
}

int main()
{
    A a;
    return 0;
}

我的问题是:你为什么要这样做?有什么优势吗?我可以看到,如果您有一些成员相同但基础不相同的课程,那会有多大帮助,但这值得麻烦吗?它不是很可读,是吗? 此外,编译器如何处理这些包含?它是否只是将标题的内容粘贴到包含它的位置(有点像宏)?

C++语言

评论

0赞 Oliver Charlesworth 9/8/2011
事实上,你为什么要这样做?你见过这种模式在任何地方使用吗?
0赞 R. Martinho Fernandes 9/8/2011
嗯,这就是预处理器是邪恶的主要原因。
11赞 Seth Carnegie 9/8/2011
嗯,这就是预处理器很棒的主要原因。
1赞 R. Martinho Fernandes 9/8/2011
@Seth:邪恶产生力量,不是吗?+1。

答:

2赞 Seth Carnegie 9/8/2011 #1

预处理器(在任何东西之前运行)在偶然发现 时,几乎从字面上复制了该标头的内容并将其粘贴到指令的位置。include#include

像你描述的那样使用它的优点很少,主要的好处是你不必复制代码。

但是,在 9999/10000 的情况下,绝对不值得麻烦。如果你在头文件中的某个地方有一个错别字,你会在每个使用它的文件中得到奇怪的错误,而且在你真正打开文件并阅读它之前,根本不清楚它在做什么。

如果可能的话,请避免这种情况。我想不出绝对必要的情况;大多数情况下,通过遗传或组合可以达到相同的效果,而不会产生所有副作用。

2赞 alfa 9/8/2011 #2

在像 Ruby 这样的语言中,这个概念被称为 Mixin。由于我们在 C++ 中有多个继承,因此我们在这里不需要它。

评论

0赞 Mankarse 9/8/2011
好吧,使用预处理器实现 mixin 比多重继承要好一些,因为它确实使客户端成为 mixin 类型的子类型。使用 includes 实现 mixin 的主要问题是 C++ 开发人员可能会发现它令人困惑(因为它不是一种惯用技术)。
1赞 R. Martinho Fernandes 9/8/2011
@Mankarse 成为mixin类型的子类型有什么问题?
1赞 Mankarse 9/8/2011
@R. Martingo Fernandes - 这意味着你被困在混音中。如果要在以后的某个阶段更改实现,则可能会导致客户端代码(例如 )中断。A a; mixin& b = a;
1赞 R. Martinho Fernandes 9/8/2011
如果您需要编写该客户端代码,它将无法与预处理器一起使用。我看不出有严格的优势。我看到了权衡取舍。
0赞 Mankarse 9/8/2011
ALFA -- 如果您使用受保护的继承或私有继承,那么客户端(实际上)根本不可能使用该功能,因此它违背了目的。@R. Martinho Fernandes:我更多地谈论的是当代码被意外编写时的情况,因为编译器允许它,即使它并不是真正面向未来的或正确的。
4赞 Jan Hudec 9/8/2011 #3

我从未在课堂上看到过这种情况,如果您前几天还想理解代码,建议您永远不要这样做。

也就是说,在一种情况下,我发现这种技术是可以接受的,那就是当你有一个大表时,你需要从中生成多个结构,如枚举和属性表。让我们有两个文件,例如:

foobars.h:

enum Foobars {
#define FOOBAR(id, description, args) FOOBAR_##id,
#include "foobars.tab"
#undef FOOBAR
};

extern const char *foobar_names[];
const char *parse_foobar(Foobars fb, const char *input);

foobars.cpp:

#include "foobars.h"
const char *foobar_names[] = {
#define FOOBAR(id, description, args) description,
#include "foobars.tab"
#undef FOOBAR
};

const char *parse_foobar(Foobars fb, const char *input) {
    switch(fb) {
#define INT get_int(&input)
#define FLOAT get_float(&input)
#define STRING get_string(&input)
#define FOOBAR(id, description, args) args
#include "foobars.tab"
#undef FOOBAR
    }
return input;

魔术就在“foobars.tab”中(它很特别,所以我建议不要将其称为 anything.h 或 anything.hpp 或任何其他常见的后缀):

/* CAUTION! This file is included using C preprocessor in the middle of various structures
 * It must not contain anything except definitions of foobars in the FOOBAR macro and
 * comments. Don't forget NOT to write semicolons; some of the structures are
 * comma-separated and some semicolon-separated. FOOBAR will be defined appropriately before
 * including this file. */
FOOBAR(NULL, "Empty command, does nothing", {}) // NO semicolon!
// Also some preprocessors don't like empty arguments, so that's why {}.
// (void)0 is an alternative.
FOOBAR(FOO, "Foo bars and bazes", a = INT; b = STRING)
FOOBAR(BAR, "Bars, but does not baz", x = FLOAT)
...

另一种选择是定义特殊包含内容的宏。如果表很短,则宏更易于阅读,但如果文件很长,则特殊文件更有意义。

最后一种选择是以完全不同的格式生成表并生成代码,但这涉及编写一些特殊的脚本来构建它,而这并没有。

评论

0赞 Andreas 2/2/2017
“我从来没有在课堂上见过这个”......我也没有。直到昨天,当我查看外部DLL的标头时,该标头已完全具有此功能。我一直在寻找我们在代码中调用的函数声明,但在包含的标头中找不到它。然后我发现这在课堂上包括,然后我理解了。那里有一些非常奇怪的人...... :x
2赞 Derek 1/9/2013 #4

我发现的一个用法是,如果您想在类定义中自动生成大量行,那么像这样包含自动生成的文件可能会有所帮助。

评论

0赞 Felix F Xu 6/9/2023
是的,在 QT 中,它使用在源文件的末尾并生成。github.com/qt/qtbase/blob/dev/src/corelib/kernel/......#include "moc_qobject.cpp"moc_qobject.cpp
0赞 mike 5/5/2017 #5

这些答案都非常古老,但我找到了上面没有列出的理由。我正在编写自动化测试,这些测试需要访问私人成员,因此在许多课程中使用友谊声明。 由于友谊不是继承的,因此我必须在与它交互的每个类中显式声明任何新测试类的友谊。

如果有一个或多个文件按照“test_friends.h”列出我的测试类,这要容易得多:

friend class testOneFeature;
friend class testAnotherFeature;

等等,在测试的类中,我可以简单地将该文件包含在声明中。

class MyClass
{
#include "test_friends.h"
public:
 //etc 
};
0赞 Lee Dilkie 1/3/2022 #6

只是偶然发现了这一点,它对 gtest 夹具非常有用,因为您需要将每个测试夹具声明为 FRIEND_TEST() 才能访问私有/受保护的成员。