【问题标题】:C++ header-implementation-header-implementation dependency chainC++ header-implementation-header-implementation 依赖链
【发布时间】:2011-11-24 17:15:29
【问题描述】:

我正在尝试使用依赖解析器创建简单的 C++ 增量构建工具。 我一直对 cpp 构建过程的一个问题感到困惑。 假设我们有一个包含几个文件的库:

// h1.h
void H1();

// s1.cpp
#include "h1.h"
#include "h2.h"
void H1(){ H2(); }

// h2.h
void H2();

// s2.cpp
#include "h2.h"
#include "h3.h"
void H2(){ /*some implementation*/ }
void H3(){ /*some implementation*/ }

// h3.h
void H3();

在包含 h1.h 的客户端代码中时

// app1.cpp
#include "h1.h"
int main()
{
  H1();
  return 0;
}

存在 s2.cpp 实现的隐式依赖: our_src -> h3 -> s1 -> h2 -> s2。所以我们需要链接两个obj文件:

g++ -o app1 app1.o s1.o s2.o

包含 h3.h 时的对比

// app2.cpp
#include "h3.h"
int main()
{
  H3();
  return 0;
}

只有一个源依赖: our_src -> h3 -> s2

所以当我们包含 h3.h 时,我们只需要编译 s2.cpp(尽管包含 s1.cpp -> h2.h):

g++ -o app2 app2.o s2.o

这是一个非常简单的问题示例,在实际项目中,我们肯定有数百个文件,而低效的包含链可能包含更多文件。

所以我的问题是:当我们检查依赖项(没有 CPP 解析)时,是否有方法或工具可以找出可以省略哪些标头包含?

如果有任何回应,我将不胜感激。

【问题讨论】:

    标签: c++ compiler-construction build incremental-build


    【解决方案1】:

    在您声明要查看对 s2.cpp 的隐式依赖的情况下,您需要解析实现模块 s1.cpp,因为只有在那里您才会发现 s1 模块正在使用 s2。所以对于“我可以在不解析 .cpp 文件的情况下解决这个问题吗”这个问题,答案显然是否定的。

    顺便说一句,就语言而言,您可以放入头文件或实现文件中的内容没有区别。 #include 指令在 C++ 级别不起作用,它只是一个文本宏函数,对语言没有任何理解。 此外,即使解析“只是”C++ 声明也是一场真正的噩梦(C++ 语法的困难部分是声明,而不是语句/表达式)。

    也许您可以使用 gccxml 的结果来解析 C++ 文件并返回可以检查的 XML 数据结构。

    【讨论】:

    • 感谢您的回复!情况并没有我预想的那么糟糕。我已经尝试过 rbgccxml(Ruby 接口),发现这个解决方案只有一个缺点:它需要 gcc,而且将 gccxml 与其他编译器一起使用不太好。
    • @Bombazook:回复“感谢您的回复!”:在此站点表示感谢的适当方式是为好的答案投票并接受您的想法最佳答案。
    • @David Hammen:对不起,但我的声誉不接受我投票。
    【解决方案2】:

    这不是一个简单的问题。让这变得困难的原因有很多:

    1. 如果在 N>1 个源文件中实现一个头文件怎么办?例如,假设class Foofoo.h 中定义,但在foo_cotr_dotr.cppfoo_this_function.cppfoo_that_function.cpp 中实现。
    2. 如果在多个源文件中实现了相同的功能怎么办?例如,假设Foo::bar()foo_bar_linux.cppfoo_bar_osx.cppfoo_bar_sunos.cpp 中有实现。要使用的实现取决于目标平台。

    一个简单的解决方案是构建一个共享或动态库并链接到该库。让工具链解决这些依赖关系。问题 #1 完全消失了,如果你有足够聪明的 makefile,问题 #2 也会消失。

    如果您坚持反对这个简单的解决方案,您将需要自己解决这些依赖关系。您可以通过项目规则一个头文件 == 一个源文件来消除上述问题(不是详尽的列表)。我见过这样的规则,但不像我见过的项目规则那样频繁,即一个函数 == 一个源文件。

    【讨论】:

    • 我知道我正在尝试重新发明轮子,但我讨厌糟糕的 makefile 语法,并且一开始总是尝试静态链接。您的解决方案可以帮助我解决当前 cpp 项目的问题,但我想让它在未来变得更容易。所以我想基于 gccxml 或 SWIG 之类的东西创建自己的依赖解析器。
    【解决方案3】:

    你可以看看我是如何实现Wand 的。它使用指令为各个源文件添加依赖项。文档还没有完全完成,但是 Gabi 的源代码中有 Wand 指令的例子。

    示例

    线程类包含文件

    Thread.h 在链接时需要 thread.o

    #ifdef __WAND__
    dependency[thread.o]
    target[name[thread.h] type[include]]
    #endif
    

    windows上的线程类实现(thread-win32.cpp)

    这个文件应该只在 Windows 是目标平台时编译

    #ifdef __WAND__
    target[name[thread.o] type[object] platform[;Windows]]
    #endif
    

    GNU/Linux 上的线程类实现 (thread-linux.cpp)

    仅当 GNU/Linux 为目标平台时才应编译此文件。在 GNU/Linux 上,链接时需要外部库 pthread。

    #ifdef __WAND__
    target
        [
        name[thread.o] type[object] platform[;GNU/Linux]
        dependency[pthread;external]
        ]
    #endif
    

    优点和缺点

    优点

    • Wand 可以扩展为适用于其他编程语言
    • Wand 只需发出命令 wand 即可保存成功链接新程序所需的所有必要数据
    • 项目文件不需要提及任何依赖项,因为它们存储在源文件中

    缺点

    • Wand 在每个源文件中都需要额外的指令
    • 该工具尚未被图书馆作者广泛使用

    【讨论】:

      猜你喜欢
      • 2011-05-10
      • 2022-10-06
      • 1970-01-01
      • 1970-01-01
      • 2021-10-09
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 2016-06-27
      相关资源
      最近更新 更多