【问题标题】:How to avoid namespace collision in C and C++如何避免 C 和 C++ 中的命名空间冲突
【发布时间】:2013-09-28 13:01:56
【问题描述】:

我可以使用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,二进制文件必须在多个平台上运行吗?这种情况下会不会有命名空间冲突?

【问题讨论】:

  • #include_next 到底是什么?
  • @AlexandreC。一个GNU extension。我会说,这肯定不是“传统方法”。
  • 嗯,我以为它首先出现在 Clang 中,但可能是错误的。
  • 传统? #include_next 的使用会导致极大的混乱。我们建议仅在没有其他选择时使用它。特别是,它不应该用在属于特定程序的头文件中;它应该仅用于按照fixincludes 进行全局更正。 ― GCC 手册。
  • using namespace 经常会给你带来很多问题。尽量不要使用它。相反,您可以使用例如using std::vector。不要包含整个命名空间。

标签: c++ c gcc namespaces


【解决方案1】:

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

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

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

通过系统化可以轻松防止文件名冲突:organize your headers so that they mimic your namespaces,例如boost::numeric::ublas::vector&lt;&gt; 来自 #include &lt;boost/numeric/ublas/vector.hpp&gt;。并且不要将不同库的头文件和源代码堆放在一个目录中,以便您可以使用不同的目录前缀包含具有相同名称的头文件,例如#include &lt;lzma/version.h&gt;#include &lt;linux/version.h&gt;.

【讨论】:

    【解决方案2】:

    解决此问题的正确方法是设置您的构建环境,以减少名称冲突的可能性。

    编写库的指南

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

    考虑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 这样的临时修复来修复物理冲突或预处理器定义来修复逻辑冲突。他们是肮脏的黑客,虽然他们可能会暂时解决你的问题,但他们很可能最终会回来咬你。

    【讨论】:

      【解决方案3】:

      文件名冲突

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

      #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 idiom 隔离与每个库交互的代码,这样冲突的标识符就不会因为传递包含而被拖入数以千计的项目中。

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

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

      【讨论】:

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

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

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

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

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

      当然,最好的办法是一开始就注意避免这些问题。

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-03-17
          • 1970-01-01
          • 1970-01-01
          • 2012-12-18
          • 1970-01-01
          • 2011-08-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多