【问题标题】:Splitting coupled classes into separate headers将耦合类拆分为单独的标头
【发布时间】:2018-08-26 07:19:17
【问题描述】:

我正在尝试将以下两个类分开,以便每个类都可以在其自己的 Header 中定义:


#include <iostream>

class Boo;
class Foo
{
public:
    Foo(Boo *booPtr)
    :booPtr(booPtr){};
    virtual ~Foo(){};
    Boo *booPtr;
};

class Boo
{
public:
    Boo()
    :foo(this){};
    virtual ~Boo(){};
    Foo foo;
    int num = 32;
};

int main()
{
    Boo *boo = new Boo;
    std::cout << "booPtr : " << boo->foo.booPtr->num << '\n';
    delete boo;
}

结果:

booPtr : 32
Program ended with exit code: 0

这是我失败的分离尝试:

"Foo.hpp"

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>
#include "Boo.hpp"

class Foo
{
public:
    Foo(Boo *booPtr)
    :booPtr(booPtr){};
    virtual ~Foo(){};
    Boo *booPtr;
};

#endif /* Foo_hpp */

"Boo.hpp"

#ifndef Boo_hpp
#define Boo_hpp

#include <stdio.h>
#include "Foo.hpp"

class Boo
{
public:
    Boo()
    :foo(this){};
    virtual ~Boo(){};
    Foo foo; // Error : Field has incomplete type 'Boo'
    int num = 32;
};

#endif /* Boo_hpp */

"main.cpp"

#include <iostream>
#include "Foo.hpp"
#include "Boo.hpp"

int main()
{
    Boo *boo = new Boo;
    std::cout << "booPtr : " << boo->foo.booPtr->num << '\n';
    delete boo;
}

但我无法构建代码,因为它会生成以下错误:

Boo.hpp -> Foo foo; -> "字段的类型不完整 'Boo'"

如何修复我的代码?

【问题讨论】:

标签: c++ class header-files circular-dependency forward-declaration


【解决方案1】:

您有一个文件包含另一个文件,反之亦然。这就形成了一个包容的循环。

意识到#include 宏除了用另一个文件替换它自己(即它的行)之外什么都不做。这很明显为什么您不能让文件 A 包含文件 B。

显而易见的解决方案是在 Foo 中提出 Boo 的前向声明:

#ifndef Foo_hpp
#define Foo_hpp

#include <stdio.h>
class Boo;

class Foo
{
public:
...

奇怪的是,你在分开之前就已经这样做了。

现在为您提供更多理论:从技术上讲,一个类是一种数据存储。它需要知道它的大小才能保留内存。因此,它需要知道它的成员的所有大小,只有在声明它们时才能知道。因此,一个类需要包含它作为成员的每个类的声明头。但是,指向对象的指针是不同的(引用也是如此)。指针始终采用相同的大小,即 32 位或 64 位,具体取决于您的平台(可能是 64 位,因为我们现在有 64 位平台)。因此,类不需要知道它指向的类,它为其指针成员保留的内存总是相同的大小。这就是为什么在这里可以使用前向声明(不涉及类大小)的原因。

【讨论】:

  • @Aconcagua 是的,但我不想在国外走得太远。让我们保持一些基础知识。加上我的文字没有错,只是没有提到参考。
  • 隐藏另一半可能给人的印象是它与引用不同,所以我仍然(只是)以 “指针(以及引用)”的风格提及它们“。如果我想迂腐,我可以抱怨指针大小,因为即使在今天,也有一些 16 位微控制器具有 16 位指针...
  • @Aconcagua:凯,因为它不疼,我把它放在答案中。但是请意识到,当人们学习新东西时,把事情简单化是关键。我知道您在 c++ 方面非常能干,很可能比我更有经验,但不要陷入对初学者过于技术化的陷阱。
  • 完全同意。但是提及引用可能会阻止 初学者 将此引用转换为指针,因为(错误地)相信她/他需要能够使用预先声明的唯一类型...
【解决方案2】:

虽然循环包含本身,假设正确的包含保护不是错误,但请记住它只是文本替换:

确保所有排列都是可行的并且具有相同的含义!

在您的情况下,将 "Boo.hpp" 的包含替换为您在合并源中出于某种原因使用的前向声明。

或者,你知道,干脆放弃拆分标题:
请记住,类不一定是适当的组织单位。

【讨论】:

  • 我的意思是,我可以想象一个系统不会将每个类分成自己的文件,但在大多数情况下,我们会这样。另外,特别是对于初学者,我强烈建议使用每个类一个标题的原则。就像一个经验法则一样,只有在有充分理由的情况下才会打破,就像我们在编程中发现的其他一些规则一样。
  • @Aziuth 两段代码耦合度越高,将它们分成不同的标头就越不合理,因为它不会降低复杂性或编译时间。许多程序员,尤其是新手,必须避免的一个有缺陷的想法是,类是最重要的东西,自然是组织单位。将紧密耦合的代码分成多个文件几乎没有任何意义,即使许多 IDE 的模板都鼓励这样做。
  • 我喜欢阅读更多相关信息。我自己的风格几乎是基于类。你能给我推荐一些文章或博客或类似的东西吗?
  • 在很多紧密耦合类的情况下,将一个类作为另一个类的嵌套类甚至更合适,例如。 G。链表的Node 类。
【解决方案3】:

关于循环包含的附录,以 Foo.hpp 开头:

#ifndef FOO
#define FOO  // (!)

// now including BOO!
#ifndef BOO
#define BOO

// now including FOO  a g a i n
// as FOO  i s  already defined, all that remains is:
#ifndef FOO
#endif

class Boo { }; // now lacking definition of Foo...

#endif // BOO

class Foo { };

#endif // FOO (first one)

与 Boo.hpp 类似(您只需要预先声明 Foo,但没有,因为定义再次遵循之后......)。

如您所见,循环包含,提供适当的包含保护,不会导致无休止的自我包含。然而,结果并不是你想要的。

【讨论】:

  • 出于好奇 - 这看起来很像有人不应该这样做。我错了吗?你能举个例子吗?
  • @Aziuth 假设您有两个模板类,每个类都引用另一个。然后您可以定义类,但不使用其他模板实现这些功能,then 包含其他标头,then 提供实现。
  • 啊,当然,是的。没有因为那些不规则而跳跃,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 2013-08-02
  • 1970-01-01
相关资源
最近更新 更多