【问题标题】:Different C++ Class Declarations不同的 C++ 类声明
【发布时间】:2014-08-21 13:18:06
【问题描述】:

我正在尝试使用不使用命名空间并导致符号冲突的第三方 C++ 库。冲突符号用于我的代码未使用的类,因此我正在考虑为第三方库创建自定义头文件,其中类声明仅包含我的代码正在使用的公共成员,而忽略任何使用冲突类的成员。基本上是创建一个界面。

我有三个问题:

  1. 如果编译为 .obj 文件有效,当我开始链接时,这种技术是否仍会导致符号冲突?

  2. 如果这不是问题,链接时不同的类声明会导致问题吗?例如,链接器是否验证每个 .obj 文件使用的类的声明具有相同数量的成员?

  3. 如果这两个都不是问题并且我能够链接 .obj 文件,那么在调用方法时会导致问题吗?我不知道 C++ 到底是如何工作的,但是如果它使用索引来指向类方法,并且这些索引从一个 .obj 文件到另一个不同,我猜这种方法会在运行时崩溃。

【问题讨论】:

  • 当你有同名但不同定义的类时,我认为它是 UB。你应该把你的代码放到一个独特的命名空间中来解决问题,而不是试图绕过它。
  • 如果我正确理解您的问题,您将在链接时遇到问题。
  • 你有没有第三方源?
  • 您能告诉我们那个第三方库的名称,以便我们避免它吗?不使用任何命名空间在 20 年前是可以接受的,但现在不行。
  • 这是 Haxe C++ 目标 (hxcpp)。它在全局命名空间中有 String、File 和 Class 等类。我将创建一个请求,将它们移到子命名空间中,但看起来这将是一项艰巨的任务。

标签: c++ class compilation declaration static-linking


【解决方案1】:

您可以使用巧妙的技巧将整个库移动到命名空间中来处理导入。导入指令所做的只是将相关代码复制到当前的“翻译单元”(当前代码的花哨名称)。你可以利用这一点

我从用户JohnB 的另一个答案中大量借用,后来被他删除了。

// my_thirdparty.h
namespace ThirdParty {
  #include "thirdparty.h"
  //... Include all the headers here that you need to use for thirdparty.
}

// my_thirdparty.cpp / .cc 
namespace ThirdParty {
  #include "thirdparty.cpp"
  //... Put all .cpp files in here that are currently in your project
}

最后,从您的项目中删除第三方库中的所有 .cpp 文件。只编译 my_thirdparty.cpp。

警告:如果您包含来自单个 my_thirdparty.cpp 的多个库文件,这可能会由于各个 .cpp 文件之间的交互而引入编译器问题。诸如包含命名空间或错误的定义/包含指令之类的事情可能会导致这种情况。解析或创建多个 my_thirdparty.cpp 文件,在它们之间拆分库。

【讨论】:

    【解决方案2】:

    我建议使用包装器来封装第三方库。

    包装器.h

    #ifndef WRAPPER_H_
    #define WRAPPER_H_
    
    #include <memory>
    class third_party;
    
    class Wrapper
    {
    public:
        void wrappedFunction();
        Wrapper();
    private:
    // A better choice would be a unique_ptr but g++ and clang++ failed to
    // compile due to "incomplete type" which is the whole point
            std::shared_ptr<third_party> wrapped;
    };
    #endif
    

    包装器.cpp

    #include "Wrapper.h"
    #include <third_party.h>
    
    void Wrapper::wrappedFunction()
    {
         wrapped->command();
    }
    
    Wrapper::Wrapper():wrapped{std::make_shared<third_party>()}
    {
    }
    

    unique_ptr 不起作用的原因在这里解释:std::unique_ptr with an incomplete type won't compile

    【讨论】:

    • 这实际上是我开始做的,然后开始希望我可以通过创建标题而不编写中间人代码来实现。由于疯狂的标头想法行不通,因此创建这样的包装器看起来是我最好的选择,所以我想将此标记为答案,但我的问题是更多地了解为什么更简单的标头方法可能不会工作。
    • @silentorb 仅当您需要的函数或类都不依赖于任何冲突的类时,标头解决方案才有效。此解决方案还允许您在不更改“任何”代码的情况下替换third_party。
    【解决方案3】:

    为了解释的目的,下面是一个简化的解释。

    c++ 允许你使用你声明的函数。您所做的是将多个定义放在跨多个翻译单元的单个声明中。如果您在头文件中公开类声明,您的编译器会在每个翻译单元中看到这一点,包括头文件。

    因此,您自己的类函数必须完全按照已声明的方式定义(相同的函数名称相同的参数)。 如果函数没有被调用,你可以不定义它,因为编译器不知道它是否可能在另一个翻译单元中定义。

    编译会为目标代码中定义的每个函数(符号)创建标签。另一方面,为每个引用的符号创建一个未解析的标签(调用站点、变量使用)。

    因此,如果您遵循此规则,您应该会到达您的代码编译但无法链接的地步。链接器是将定义的符号从每个翻译单元映射到符号引用的工具。

    如果链接在一起的目标文件对同一函数有多个定义,则链接器无法创建精确匹配,因此无法链接。

    在实践中,您很可能希望提供一个库并享受使用您自己的类的乐趣,而无需打扰您的用户可能定义的内容。尽管程序员特别注意将事物放入命名空间,但两个用户仍可能为命名空间选择相同的名称。这将导致链接失败,因为编译器暴露了符号并且应该链接它们。

    gcc 添加了一个属性来显式标记符号,该符号不应暴露给链接器。 (叫attribute hidden (see this SO question)) 这使得同名类可以有多个定义。 为了让它在编译单元中工作,你必须确保类声明没有暴露在接口头中,因为它可能导致多个不匹配的声明。

    【讨论】:

      【解决方案4】:

      理论上,您需要相同的声明才能使其工作。

      在实践中,您肯定需要确保您的声明包含:

      • 您使用的所有方法
      • 所有虚拟方法,使用与否。
      • 所有数据成员

      您也需要按照正确的声明顺序进行所有这些操作。

      您可能会通过伪造数据成员侥幸逃脱,但需要确保放入大小相同的存根。

      如果您不执行所有这些操作,您将无法获得相同的对象布局,并且即使链接有效,它也会在运行时快速而严重地失败。

      如果您这样做,对我来说仍然存在风险,最坏的情况可能会起作用,但会出现奇怪的运行时故障。

      “如果它使用索引”:在某种程度上,虚拟函数的工作方式是由实现定义的,但通常它确实使用虚拟函数表的索引。

      你可以做的是:

      • 获取原始标题
      • 保留您使用的类的完整声明
      • 将您不使用但被您使用的类和声明引用。
      • 删除所有未引用的类型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-03
        • 1970-01-01
        • 1970-01-01
        • 2019-10-11
        相关资源
        最近更新 更多