【问题标题】:Avoiding duplicate symbol due to initialization of specialization in header?由于标题中的专业化初始化而避免重复符号?
【发布时间】:2018-06-06 22:50:12
【问题描述】:

我捕获了由于我正在尝试在标题中提供的定义而捕获重复的符号错误。这是来自Minimal, Complete, and Verifiable example 的错误。头文件和源文件如下所示。

$ clang++ main.cpp x.cpp y.cpp -o main.exe 2>&1 | c++filt
duplicate symbol Id<S>::id in:
    /tmp/main-3f2415.o
    /tmp/long long-d62c28.o
duplicate symbol Id<T>::id in:
    /tmp/main-3f2415.o
    /tmp/long long-d62c28.o
duplicate symbol Id<S>::id in:
    /tmp/main-3f2415.o
    /tmp/unsigned long long-bfa6de.o
duplicate symbol Id<T>::id in:
    /tmp/main-3f2415.o
    /tmp/unsigned long long-bfa6de.o
ld: 4 duplicate symbols for architecture x86_64

这里是一个类似的问题,但它不涉及专业化:Static member initialization in a class template。这个问题具有专业化,但它适用于MSVC而不是Clang:How to initialize a static member of a parametrized-template class。这个问题声明将它放在源 (*.cpp) 文件中,但我们的目标是让头文件避免 Clang 3.8 和 'Id&lt;S&gt;::id' required here, but no definition is available 警告:Where should the definition of an explicit specialization of a class template be placed in C++?

GCC、ICC、MSVC、SunCC 和 XLC 都可以初始化。 Clang 和 LLVM 给我带来了麻烦。 Clang 和 LLVM 在专业化和 extern 的显式实例化方面也存在问题,所以它有自己的特殊地狱。

我们支持C ++ 03虽然C ++ 17,所以我们必须小心解决方案。我天真地尝试将特化的初始化放在一个未命名的命名空间中,以防止符号转义翻译单元,但这会导致编译错误。

在头文件中初始化和特化模板类时,我们如何避免重复的符号定义?


下面是 MCVE,它是 cat main.cpp a.h s.h s.cpp t.h t.cpp x.cpp y.cpp。问题似乎在a.h,它提供专业化和初始化;和源文件x.cppy.cpp,其中包括a.h

main.cpp

#include "a.h"
#include "s.h"
#include "t.h"

int main(int argc, char* argv[])
{
    uint8_t s = Id<S>::id;
    uint8_t t = Id<T>::id;
    return 0;
}

a.h

#ifndef A_INCLUDED
#define A_INCLUDED

#include <stdint.h>

template <class T> class Id
{
public:
    static const uint8_t id;
};

class S;
class T;

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

#endif

s.h Em>

#ifndef S_INCLUDED
#define S_INCLUDED

class S {
public:
    S();
};

#endif

s.cpp Em>

#include "s.h"

S::S() {}

t.h Em>

#ifndef T_INCLUDED
#define T_INCLUDED

class T {
public:
    T();
};

#endif

t.cpp em>

#include "t.h"

T::T() {}

x.cpp

#include "a.h"

y.cpp

#include "a.h"

【问题讨论】:

  • inline 没有帮助吗?
  • @user0042 - 不,内联编译失败。它在课程和专业上都失败了。

标签: c++ templates initialization static-members template-specialization


【解决方案1】:

Clang/LLVM 不是问题。您只是遇到了未定义的行为,无需进行任何诊断。修复很简单。您需要将您的专业放在一个翻译单元中。即,

a.cpp:

#include "a.h"

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

然后是命令行:

clang++ main.cpp a.cpp x.cpp y.cpp -o main.exe 2>&1 | c++filt

然后瞧。它有效。

【讨论】:

  • 谢谢。没有a.cpp。我们如何将它保存在标题中?还是在 Clang 和 LLVM 下根本不可能做到这一点? (我尝试使用 *.cpp 文件,但 Clang 在实例化时抱怨缺少定义)。
  • 您不要将它保存在包含 multiple 次的标题中。您有多个定义错误因为您在不同的翻译单元中多次包含a.h。相反,您要做的是向前声明您必须做的事情,并在专用的*.cpp 中实例化您可以做的事情。它可能可以一次性编译和链接您的整个项目,但不能保证——“翻译单元”的定义有些模糊(即它不一定 相当于单个*.cpp 文件)。
  • @user268396 抱歉,这是错误的。翻译单元在[lex.separate] 中明确定义:“程序的文本保存在本文档中称为源文件的单元中。源文件以及通过预处理指令#include 包含的所有头文件和源文件,减去任何源代码行被任何条件包含预处理指令跳过的,称为翻译单元。”不过,关于在单个 *.cpp 文件中实例化的部分是正确的。
  • @user9139968 - 这是我似乎误解的问题之一。 id 是一个 staticconst 变量,所以我希望它有内部链接并且不会逃脱翻译单元。但我也明白这不是文件范围。因此,我想使用匿名命名空间,但导致编译错误。
  • @jww 您仍然需要在标头中使用 template&lt;&gt; const uint8_t Id&lt;S&gt;::id; 来声明 id 的显式实例化,同时在源文件(.cpp 或 .h仅包含一次)。也许在支持的地方设置 ID constexpr也可能是一个解决方案。
【解决方案2】:

您违反了 Id::id 和 Id::id 的 ODR(一个定义规则)。它们是为每个包含它们的翻译单元定义的,因此在您链接时会显示出来。

根据您对 S 和 T 类 ID 的意图,您必须为它们提供一个独特的家。一种可能是将它们停放在 main.cpp 中。 ma​​in.cpp 添加

template<> const uint8_t Id<S>::id = 0x01;
template<> const uint8_t Id<T>::id = 0x02;

或者,将S的id放入s.cpp,将T的id放入t.cpp:

s.cpp

#include "s.h"
#include "a.h"

template<> const uint8_t Id<S>::id = 0x01;

和 t.cpp 的等价物。

不要忘记删除 a.h. 中的任何 S 和 T 痕迹

但是,如果它们是 a.h 接口的一部分,创建一个 a.cpp 并在那里定义它们。

【讨论】:

    【解决方案3】:

    您可以使用unnamed namespace,它可以使您的班级具有内部联系,例如

    #ifndef A_INCLUDED
    #define A_INCLUDED
    
    #include <stdint.h>
    
    namespace {
        template <class T> class Id
        {
        public:
            static const uint8_t id;
        };
    }
    
    class S;
    class T;
    
    template<> const uint8_t Id<S>::id = 0x01;
    template<> const uint8_t Id<T>::id = 0x02;
    
    #endif
    

    将此代码替换为您示例中a.h中的内容,然后它将起作用,因为由于内部链接,一个翻译单元中的Id&lt;T&gt;与另一个翻译单元中的内容不同,因此不违反单定义规则.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-01
      • 1970-01-01
      • 2010-12-20
      • 1970-01-01
      • 2018-02-17
      • 1970-01-01
      • 2020-06-30
      • 1970-01-01
      相关资源
      最近更新 更多