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

C++ 如何停止通过分层包含传播声明?,c++,c,header-files,C++,C,Header Files,每当我创建.h头文件时,我就会想到一个问题:如何停止通过分层包含传播声明?假设有以下文件: 福安 酒吧 动物园 在文件zoo.h中,我们可以访问声明的元素foo_c,foo_t,foo(),对foo.h的每次更改都将重新编译zoo.h 我知道我们可以将实现移到.cpp文件中,但是在.h文件中的类定义中编写的代码呢?如果程序员需要,我们如何强制他在zoo.h中显式地包含foo.h呢 例如,在Qt中,当我包括并使用时,我无法访问QList,其中QQueue由QList继承,我必须明确包括。(另外,我

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

福安

酒吧

动物园

在文件zoo.h中,我们可以访问声明的元素
foo_c
foo_t
foo()
,对foo.h的每次更改都将重新编译zoo.h

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


例如,在Qt中,当我包括并使用
时,我无法访问
QList
,其中
QQueue
QList
继承,我必须明确包括
。(另外,我不知道它是如何完成的,以及它对编译时间的影响)

我将以这种方式重写代码:

福安

foo.cpp

#include "foo.h"

inline int foo()
{
    return 1;
}
酒吧

bar.cpp

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

inline int bar()
{
    return foo();
}
动物园

动物园

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

inline int zoo()
{
    // cannot *incidentally* access foo() here, explicit #include "foo.h" needed
    return bar();
}
这样,您可以在头文件中只公开接口,而实现细节仍保留在.cpp文件中


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

也许您可以使用名称空间:

福安

酒吧

动物园

这个例子看起来很做作,但可能只是因为代码很短。如果您的应用程序包含更多的代码,这个技巧可能会很有用

更新

同样的原则也适用于类和其他名称。例如,使用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_队列.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开发人员实际做了什么,它只显示了他们可以做什么。

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

福安

酒吧

动物园

富科

酒吧c

动物园

介于两者之间的一种方式是引入一个附加级别的源文件,您可以将其称为
.inl
,将函数实现移动到那里并使其内联。通过这种方式,您可以将这些新文件包含在原始标题之后,并且仅在实际需要的地方包含,从而获得有限的可见性和最大的内联。不过,我认为不值得这么做

模板会使事情更加复杂,因为在一般情况下,只要模板需要实例化,定义就必须可用。有一些方法可以控制这一点,例如,通过强制实例化所需的专门化,以避免包含每个使用点的定义,但同样,增加的复杂性可能不值得

<>如果你担心编译时间,通常你更容易依赖编译器的头预编译机制。

< P> C++和C,“停止传播声明”,你需要从公共接口,时间段中删除它们。将它们转移到实施阶段。或“不太公开”的界面

编译时间是目标之一。其他是便携性、可维护性。这也直接关系到

<>最能帮助你的类派生的C++技术是派生实现类,将相应的头包含到实现cpp中,并在公共接口中向前声明实现。用户对基类一无所知,只知道实现的名称

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


每一个界面设计都是一种简单性(或努力性)和灵活性之间的折衷。如果您需要归档前两个,请使用更复杂的方法。您可以提供两个公共接口:一个用于使用,另一个用于扩展,范围更广,级别更低

我发现在我的代码中,清楚地区分前向声明和定义是很重要的:尽可能多地使用前向声明

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

例如,如果X不从Y中生成子类,并且X不包含任何类型为Y的成员,则不需要包含Y.hpp。正向声明类Y就足够了。有时,为了更好地解耦我的代码,我将保留一个指向Y的引用或指针,而不是将Y嵌入到类X中——如果这是可行的,那么我所需要做的就是向前声明类Y

现在,有一条评论是关于在使用模板类时不能进行前向声明。但是有一个技巧围绕着这个-而不是使用typedef,你想要的模板实例化的子类,例如:

class Bars : public std::vector<Bar> { };
类栏:公共std::vector{};
现在您可以向前声明
类栏,在此之前您无法向前声明
std::vector

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

  • 将我的代码划分为按名称空间划分的模块
  • 在包含该模块转发声明的每个模块中创建一个fdecl.hpp文件
  • 强烈建议使用
    #include
    
    #include "zoo.h"
    #include "bar.h"
    
    inline int zoo()
    {
        // cannot *incidentally* access foo() here, explicit #include "foo.h" needed
        return bar();
    }
    
    namespace f {
        inline int foo();
    }
    
    #include "foo.h"
    inline int bar()
    {
        using namespace f;
        return foo();
    }
    
    #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();
    }
    
    namespace some_obscure_name
    {
        class QList {...};
        class QQueue: public QList {...}
        ...
    }
    
    #include "qt_main.h"
    using some_obscure_name::QList;
    
    #include "qt_main.h"
    using some_obscure_name::QQueue;
    
    #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
    
    #ifndef FOO_H
    #define FOO_H
    
    typedef int foo_t;
    
    int foo();
    
    class foo_c {};
    
    #endif  /* FOO_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 */
    
    #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 */
    
    #include "foo.h"
    
    int foo() {
        return 1;
    }
    
    #include "bar.h"
    #include "foo.h"
    
    int bar() {
        return foo();
    }
    
    bar_c::bar_c() : my_foo_c(new foo_c()) {}
    
    #include "zoo.h"
    #include "bar.h"
    
    int zoo()
    {
        return bar();
    }
    
    zoo_c::zoo_c() : my_bar_c(new bar_c()) {}
    
    class Bars : public std::vector<Bar> { };