如何避免C和C++中的命名空间冲突

15

我可以使用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 这样的名称吗?这种情况下可能会发生命名空间冲突吗?


38
#include_next 是什么意思? - Alexandre C.
1
@AlexandreC。一个GNU扩展。我想说,这绝不是一种“常规方法”。 - jrok
15
“传统方法?”使用#include_next可能会导致极大的混淆。我们建议只在没有其他选择时使用它。特别是,不应在属于特定程序的头文件中使用它;仅应用于类似于fixincludes的全局更正。 - GCC手册。 - n. m.
或许我不是C++的铁粉,但有时候老派的方法才是完成工作的最佳选择。 - manav m-n
1
经常使用 using namespace 会给你带来很多问题。尽量不要使用它。相反,你可以使用例如 using std::vector。不要包含整个命名空间。 - zoska
显示剩余6条评论
6个回答

29

我可以使用 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>


8
正确的做法是以一种使名称冲突不太可能的方式设置您的构建环境。
编写库的指南。例如,大多数第三方库不会将普通文件引入编译器的包含路径中,而是引入一个包含文件的目录。通过为不同模块引入子目录,可以增加灵活性。
考虑 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.hpp头文件。但它们位于不同的子目录中,因此没有冲突:
#include <boost/lockfree/queue.hpp>
#include <boost/assign/std/queue.hpp>   // no clash :)

为了方便找到正确的头文件,Boost在包含文件和命名空间上使用相同的结构:无锁队列位于boost::lockfree命名空间中,而分配队列头文件中的函数则位于boost::assign。这不仅可以直接从包含文件中找到匹配的命名空间,反之亦然,还可以减少命名空间冲突的可能性,因为命名空间冲突也很可能在文件层面上表现为物理名称冲突。
您可以根据自己的项目调整这些指南:
- 不要直接将裸的包含文件引入到包含路径中。相反,将它们放在一个目录中以避免用名称污染包含路径。 - 使用目录树来进一步组织单个库中模块的包含文件。 - 尽量匹配物理结构与逻辑结构。如果可能的话,文件系统包含路径应该类似于命名空间层次结构。
这样可以避免大部分名称冲突。问题是,如果您必须使用不遵循这些规则的第三方库,并且您无法控制外部的冲突,该怎么办?
处理第三方库名称冲突的指南:
答案是要残酷并通过构建环境执行分离。通过将冲突的库移动到唯一可识别的子目录中重新组织包含路径以解决物理冲突。这通常不是关键问题。逻辑冲突需要修补和重新编译,这更加不便。但是,如果您真的在这里遇到名称冲突,这肯定表明至少一个库供应商做得不太好,您应该考虑提交错误报告。
避免像 #include_next 这样的临时解决方案来解决物理冲突或预处理器定义来解决逻辑冲突。它们是肮脏的黑客技术,虽然它们可能暂时解决您的问题,但很可能最终会反过来咬你一口。

5

文件名冲突

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

#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的函数,则您需要隔离任何依赖于这些库的代码,以便没有其他东西必须包含这些库的头文件,从而导致冲突。只有实际调用Frobnicate的.cpp(或.c)文件应该#include该库的头文件。这可以防止意外的传递包含,这通常是如何在单个编译单元中包含Frobnicate的冲突声明的。

pimpl习语通常以C ++的术语呈现,但您也可以在C中玩同样的游戏。重点是在标题中定义自己的API到库。您的API应具有相同的功能,但使用不会冲突的名称(例如,通过添加唯一前缀来避免使用命名空间)。所有代码都应该能够在没有冲突的情况下包含该标题。然后,您提供一个实现文件(.cpp或.c),该文件是唯一#包含实际库标头的文件。该实现文件基本上将带有前缀函数的调用转发到实际库函数。你所要做的就是避免这个文件中的冲突,这应该是可行的。


2
如果_libfoo和libbar都有名为Frobnicate的函数,并且该函数具有C链接,那么在最好的情况下会发生链接器符号冲突。 如果Frobnicate来自不同的.so文件,则会导致未定义行为/运行时错误。 - Maxim Egorushkin
@Maxim Yegorushkin:说得好。我不确定在Linux世界中如何处理链接器冲突。在Windows中,您可以使用.def文件在编译库时重命名有问题的符号,这样在进行最终应用程序链接时就可以避免出现问题。 - Adrian McCarthy
顺便说一句,我假设有问题的库是第三方代码,帖子作者试图将其整合到应用程序中。如果你拥有其中一个或两个冲突的库,解决方法是重新命名有问题的标识符。 - Adrian McCarthy
2
在Linux世界中,有时可以使用部分链接来解决链接器符号冲突。通过将需要冲突符号的目标文件链接到定义符号的相应目标文件,从而使冲突符号尽早得到解决(即定义目标文件永远不会在链接器命令行中一起提及)。我不确定这种方法有多普遍,我只用过一次,除了man ld中的简短描述外,没有看到太多提及。 - Maxim Egorushkin

3
首先,#include_next不是标准的C/C++语法,它是gcc扩展,并且也被Clang所支持,但我不知道其他编译器是否支持。
处理文件名冲突的唯一方法是正确使用#include "foo.h"或者#include <foo.h>。前者通常用于当前项目中的本地文件,而后者则用于系统库文件。通常情况下,#include "foo.h" 也会搜索系统库文件,但反过来则不行。
无论如何,#include "foo.h" 应该从源文件目录开始查找,可以使用#include "../foo.h" 等命令进行相对路径包含。如果在同一个项目中存在文件名冲突,则需要使用不同的编译标志设置不同的搜索路径(基本上创建子项目)。
对于符号冲突,#define#include之前是标准的做法。
当然,最好的方法是在最初就避免这些问题。

1
在C++程序中使用C语言库时,一个有用的方法是在.h文件中使用以下代码片段。
#ifdef __cplusplus
extern "C" {
#endif

<your code here...>

#ifdef __cplusplus
}
#endif

这将允许您的C++对象与C对象链接。不过我不确定反过来是否可行,所以这可能只是一个部分答案。

在我发布那篇文章后不久,我注意到了这个网址:https://dev59.com/P3NA5IYBdhLWcg3wPLP- - DAhrens

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接