如何通过分层包含停止传播声明?

时间:2011-10-19 16:25:56

标签: c++ c header-files

每当我创建 .h 头文件时,我都会想到一个问题:“如何通过分层包含停止传播声明?”假设下面有这些文件:

foo.h中

#ifndef FOO_H
#define FOO_H

typedef int foo_t;

inline int foo() { return 1; }

class foo_c {};

#endif  /* FOO_H */

bar.h

#ifndef BAR_H
#define BAR_H

#include "Foo.h"

typedef foo_t bar_t;

inline int bar() { return foo(); }

class bar_c : public foo_c {};

#endif  /* BAR_H */

zoo.h

#ifndef ZOO_H
#define ZOO_H

#include "Bar.h"

typedef bar_t zoo_t;

inline int zoo() { return bar(); }

class zoo_c : public bar_c {};

#endif  /* ZOO_H */

在文件 zoo.h 中,我们可以访问声明的元素foo_cfoo_tfoo()以及对 foo.h的每次更改< / em>将重新编译 zoo.h

我知道我们可以将实现移动到 .cpp 文件,但是 .h 文件中的类定义中编写的代码怎么样?如果需要,我们如何强制程序员在 zoo.h 中明确地包含 foo.h

作为Qt中的一个示例,当我添加并使用<QQueue>时,我无权QList QQueue继承QList并且我必须包含<QList>明确{1}} (另外,我不知道它是如何完成的,以及它对编译时间的影响)

5 个答案:

答案 0 :(得分:3)

在C ++和C中,“要停止传播声明”,您需要将它们从公共接口,句点中删除。将它们移动到实现。或者“不太公开”的界面。

编制时间是目标之一。其他是便携性,可维护性。这也与loose coupling直接相关。

最受欢迎的C ++技术可以帮助您进行类推导Pimpl idiom。派生您的实现类,在公共接口中包含相应的头文件到实现cpp和forward-declare实现。您的用户对基类一无所知,只会知道您的实现名称。

如果您想使用typedef,则无法停止传播。但是为了提供更好的可移植性和可维护性,您可以使用与Boost库有效使用相同的方法:实现定义类型(例如this one)。

每个界面设计都是extensibilityinformation hiding和简单(或努力)之间的权衡。如果你需要先归档两个使用更复杂的方法。您可以提供两个公共接口:一个用于使用,另一个用于更广泛和更低级别的可扩展性。

答案 1 :(得分:3)

我发现在我的代码中明确区分前向声明与定义非常重要:尽可能多地使用前向声明

一般来说,如果您的X类不需要知道Y类的大小,那么您只需要Y的前向声明 - 您不需要包含Y.hpp。

例如,如果X不是Y的子类,而X不包含Y类型的任何成员,那么您不需要包含Y.hpp。前向声明 Y类; 就足够了。有时候,为了更好地解耦我的代码,我会保留一个引用或指向Y而不是在类X中嵌入Y - 如果这是可行的,那么我需要做的就是前向声明类Y;

现在,有一条关于在使用模板类时无法转发声明的评论。但是有一个诀窍 - 而不是使用typedef,你想要的模板实例化的子类,例如:

class Bars : public std::vector<Bar> { };

现在您可以转发声明class Bars;,之前您无法转发声明std::vector<Bar>;

所以,这些是我在所有C ++项目中遵循的步骤:

  1. 将我的代码分成按命名空间划分的模块
  2. 在包含该模块的前向声明的每个模块中创建 fdecl.hpp 文件
  3. 强烈倾向于使用#include <modulename/fdecl.hpp>而不是#include <modulename/foo.hpp>(对定义的前向声明)
  4. 通过这种方式,标头松散耦合,我修改代码时编译时间更快。

答案 2 :(得分:2)

我会用这种方式重写代码:

foo.h中

#ifndef FOO_H
#define FOO_H

inline int foo();

#endif  /* FOO_H */

Foo.cpp中

#include "foo.h"

inline int foo()
{
    return 1;
}

bar.h

#ifndef BAR_H
#define BAR_H

inline int bar();

#endif  /* BAR_H */

bar.cpp

#include "bar.h"
#include "foo.h"

inline int bar()
{
    return foo();
}

zoo.h

#ifndef ZOO_H
#define ZOO_H

inline int zoo();

#endif  /* ZOO_H */

zoo.cpp

#include "zoo.h"
#include "bar.h"

inline int zoo()
{
    // cannot *incidentally* access foo() here, explicit #include "foo.h" needed
    return bar();
}

这样,您只在头文件中显示您的界面,并且实现细节保留在.cpp文件/中。

但请注意,如果您使用模板,此策略将失败:它们必须在标头中完全声明(否则您可能会遇到链接器问题)。

答案 3 :(得分:2)

也许你可以使用命名空间:

foo.h中

namespace f {
    inline int foo();
}

bar.h

#include "foo.h"
inline int bar()
{
    using namespace f;
    return foo();
}

zoo.h

#include "bar.h"
inline int zoo()
{
    using namespace b;
    // Cannot use foo here: can only refer to it by the full name f::foo
    return bar();
}

这个例子看起来很人为,但可能只是因为代码太短了。如果您的应用程序涉及更多代码,这个技巧可能会有所帮助。

<强>更新

同样的原则可以用于类和其他名称。例如,使用Qt名称:

qt_main.h

namespace some_obscure_name
{
    class QList {...};
    class QQueue: public QList {...}
    ...
}

qt_list.h

#include "qt_main.h"
using some_obscure_name::QList;

qt_queue.h

#include "qt_main.h"
using some_obscure_name::QQueue;

zoo.h:

#include "qt_queue.h"
...
QQueue myQueue; // OK
QList myList1; // Error - cannot use QList
some_obscure_name::QList myList2; // No error, but discouraged by Qt developers

免责声明:我对Qt没有经验;这个例子没有显示Qt开发人员实际做了什么,它只显示他们可以做什么。

答案 4 :(得分:1)

你不能吃蛋糕也吃。您可以尽可能多地利用内联,或者尽可能地限制可见性。对于类,您必须在使用派生和/或直接数据成员(需要相应的类定义可用)或间接数据成员(即指针或引用)之间取得平衡,这些成员只需要声明类。你的方法倾向于内联/直接包含,相反的极端是:

foo.h中

#ifndef FOO_H
#define FOO_H

typedef int foo_t;

int foo();

class foo_c {};

#endif  /* FOO_H */

bar.h

#ifndef BAR_H
#define BAR_H

typedef foo_t bar_t;

int bar();

class foo_c;

class bar_c {
  public:
    bar_c();
  private:
    foo_c * my_foo_c;
};

#endif  /* BAR_H */

zoo.h

#ifndef ZOO_H
#define ZOO_H

typedef bar_t zoo_t;

int zoo();

class zoo_c {
  public:
    zoo_c();
  private:
    bar_c * my_bar_c;
};

#endif  /* ZOO_H */

foo.c的

#include "foo.h"

int foo() {
    return 1;
}

bar.c

#include "bar.h"
#include "foo.h"

int bar() {
    return foo();
}

bar_c::bar_c() : my_foo_c(new foo_c()) {}

zoo.c

#include "zoo.h"
#include "bar.h"

int zoo()
{
    return bar();
}

zoo_c::zoo_c() : my_bar_c(new bar_c()) {}

介于两者之间的方法是引入一个额外级别的源文件,您可以将其称为.inl,在那里移动函数实现并使它们内联。通过这种方式,您可以在原始标题之后包含这些新文件,并且仅在实际需要的位置包含这些新文不过,我认为这不值得付出努力。

模板会使事情进一步复杂化,因为通常定义必须在模板需要实例化的任何地方都可用。有办法控制这种情况,例如:通过强制实现所需专业化的实例化,以避免包含每个使用点的定义,但同样增加的复杂性可能是不值得的。

如果您担心compilaton时间通常会更容易依赖编译器的标头预编译机制。