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

How to avoid namespace collision in C and C++

本文关键字:命名空间 冲突 C++ 何避免      更新时间:2023-10-16

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

C传统的方法是使用#include_next指令递归添加文件。如何在不使用 #include_next 指令的情况下在 C++ 中实现相同的目标,并解决应用程序和共享库之间重复文件名的问题。例如,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吗?在这种情况下,是否会发生命名空间冲突?

我可以使用命名空间指令来避免标识符/变量名称冲突

恰恰相反,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> .

解决此问题的正确方法是以较小的名称冲突方式设置构建环境。

编写库的准则

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

考虑 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附带的不同库使用了相同的概念。例如,"提升无锁定"和"提升"分配都有一个queue.hpp标头。但是它们位于不同的子目录中,因此没有冲突:

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

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

您可以根据自己的项目调整这些准则

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

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

处理第三方库名称冲突的准则

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

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

文件名冲突

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

#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包含在单个编译单元中的冲突声明的方式。

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

首先,#include_next不是标准的,是一个 gcc 扩展,也得到了 Clang 的支持,但我不知道是否有其他编译器支持它。

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

无论如何,#include "foo.h"应该从查找源文件目录开始,您可以使用#include "../foo.h"等来执行相对包含。如果在同一项目中存在文件名冲突,则必须使用不同的编译标志来设置不同的搜索路径(基本上是创建子项目(。

对于符号冲突,#define #include 之前是标准方法。

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

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

#ifdef __cplusplus
extern "C" {
#endif
<your code here...>
#ifdef __cplusplus
}
#endif

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

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