14

我可以使用using namespace指令来避免标识符/变量名冲突,但是在大型项目中发生文件名或库名冲突时会发生什么。

C常规方法中是使用#include_next指令递归地添加文件。如何在C++不使用#include_next指令的情况下实现相同的功能,并解决应用程序和共享库之间文件名重复的问题。一个示例,解决 AIX math.h 中的 class() 函数与名为“class”的标识符冲突的工作。

/* GNU Lesser GPLv2.1 or later */
#ifndef FFMPEG_COMPAT_AIX_MATH_H
#define FFMPEG_COMPAT_AIX_MATH_H

#define class some_text

#include_next <math.h>

#undef class

#endif /* FFMPEG_COMPAT_AIX_MATH_H */

编辑: 我可以使用,例如class machine-instruction-set二进制文件必须在多个平台上运行吗?在这种情况下会发生命名空间冲突吗?

4

6 回答 6

28

我可以使用 using namespace 指令来避免标识符/变量名冲突

恰恰相反,using namespace指令引入了冲突。您可以通过指定范围来解决冲突,例如std::vector<>vs. boost::numeric::ublas::vector<>

...但是当大型项目中发生文件名或库名冲突时会发生什么?

通过系统化,文件名冲突很容易防止:组织你的标题,使它们模仿你的命名空间,例如boost::numeric::ublas::vector<>来自#include <boost/numeric/ublas/vector.hpp>. 并且不要将不同库的头文件和源代码堆放在一个目录中,这样您就可以使用不同的目录前缀包含具有相同名称的头文件,例如#include <lzma/version.h>vs. #include <linux/version.h>

于 2013-09-30T13:44:32.827 回答
7

处理这个问题的正确方法是以一种不太可能发生名称冲突的方式设置您的构建环境。

编写库的指南

例如,大多数第三方库不会将纯文件引入编译器的包含路径。相反,他们引入了一个包含文件的目录。您可以通过为不同模块引入子目录来增加灵活性。

考虑Boost的目录结构。在顶层,Boost 只为包含搜索路径引入了一个名称:boost目录。这样,即使boost引入了许多可能发生冲突的头文件名(如array.hppthread.hppfunction.hpp),它们都被包裹在子目录中:

#include <boost/thread.hpp> // this is boost's header
#include "thread.hpp"       // some header specific to my current project
                            // no name clash :)

Boost 附带的不同库使用相同的概念。例如,Boost lockfree 和 Boost assign 都有一个queue.hppheader。但是它们位于不同的子目录中,因此没有冲突:

#include <boost/lockfree/queue.hpp>
#include <boost/assign/std/queue.hpp>   // no clash :)

为了更容易找到正确的头文件,Boost 对包含文件和名称空间使用相同的结构:无锁队列位于boost::lockfree名称空间中,而分配队列头中的函数则位于boost::assign. 这样,不仅可以直接从包含文件中找到匹配的命名空间,反之亦然,它还降低了命名空间冲突的可能性,因为命名空间冲突也可能体现在文件层的物理名称冲突中。

您可以根据自己的项目调整这些指南

  • 不要将裸包含文件直接引入包含路径。而是将它们组合在一个目录中,以避免使用名称污染包含路径。
  • 使用目录树进一步构建单个库中模块的包含文件。
  • 尽量使物理结构与逻辑结构相匹配。如果可能,文件系统包含路径应​​类似于命名空间层次结构。

这首先避免了大多数名称冲突。问题是,如果您必须使用不遵循这些规则的第三方库并且您遇到超出您控制范围的冲突怎么办?

处理第三方库名称冲突的指南

答案是残酷的并通过构建环境强制分离。通过将冲突的库移动到唯一可识别的子目录来重新组织包含路径以解决物理冲突。这通常是非关键的。逻辑冲突需要修补和重新编译,这很不方便。但是,如果您真的在这里遇到名称冲突,这肯定表明至少有一个库供应商没有做好他们的工作,您应该考虑提交一个错误。

远离临时修复,例如#include_next修复物理冲突或预处理器定义以修复逻辑冲突。他们是肮脏的黑客,虽然他们可能会暂时解决你的问题,但他们很可能最终会回来咬你。

于 2013-09-30T14:05:14.413 回答
4

文件名冲突

将库放在单独的子目录中,并将父目录设置为搜索位置。所以而不是:

#include "zerz.h"  // supposed to be from libfoo
#include "zerz.h"  // supposed to be from libbar

你可以这样做:

#include "libfoo/zerz.h"
#include "libbar/zerz.h"

标识符冲突

使用pimpl 习惯用法来隔离与每个库交互的代码,这样冲突的标识符就不会由于传递包含而被拖入无数项目中。

例如,如果 libfoo 和 libbar 都有一个名为 的函数Frobnicate,那么您希望隔离任何依赖于这些库的代码,这样就不必再包含这些库的头文件,从而导致冲突。只有实际调用的 .cpp(或 .c)文件才Frobnicate应该 #include 库的头文件。这可以防止意外的传递包含,这通常是最终导致Frobnicate包含在单个编译单元中的冲突声明的方式。

pimpl 习惯用法通常用 C++ 表示,但您可以在 C 中玩同样的游戏。关键是在标头中定义您自己的库 API。您的 API 应该具有相同的功能,但使用不冲突的名称(例如,如果您不能使用命名空间,则通过添加唯一前缀)。所有代码都应该能够包含该标头而不会发生冲突。然后,您提供一个实现文件(.cpp 或 .c),这是唯一包含实际库头文件的文件。这个实现文件本质上将前缀函数的调用转发给实际的库函数。您所要做的就是避免在这个文件中发生冲突,这应该是可行的。

于 2013-09-30T14:04:37.003 回答
2

首先,#include_next不是标准的,是gcc扩展,Clang也支持,但不知道有没有其他编译器支持。

处理文件名冲突的唯一方法是正确使用#include "foo.h"or 或#include <foo.h>. 前者用于当前项目的本地,而后者用于系统库。通常#include "foo.h"也会搜索系统库,但不会反过来。

无论如何,#include "foo.h"应该从查看源文件目录开始,你可以使用#include "../foo.h"etc. 来做相对包含。如果您在同一个项目中有文件名冲突,您将不得不使用不同的编译标志来设置不同的搜索路径(基本上是制作子项目)。

对于符号冲突,#definebefore#include是标准方法。

当然,最好的方法是首先注意避免此类问题。

于 2013-09-28T13:22:29.783 回答
1

在 C++ 程序中使用 C 包含时,一个有用的方法是在 .h 文件中使用以下代码片段。

#ifdef __cplusplus
extern "C" {
#endif

<your code here...>

#ifdef __cplusplus
}
#endif

这将允许您的 C++ 对象与 C 对象链接。我不知道要走另一条路,所以也许这只是部分答案。

于 2013-10-04T19:39:31.190 回答
0

“我可以使用using namespace指令来避免标识符/变量名冲突”:不!您应该避免使用该指令以避免名称冲突。

于 2014-08-15T17:45:17.323 回答