由于标头中的专业化初始化而避免重复的符号?

时间:2017-12-25 23:42:12

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

由于我试图在标题中提供的定义,我正在捕捉重复的符号错误。这是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<S>::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

#ifndef S_INCLUDED
#define S_INCLUDED

class S {
public:
    S();
};

#endif

s.cpp

#include "s.h"

S::S() {}

t.h

#ifndef T_INCLUDED
#define T_INCLUDED

class T {
public:
    T();
};

#endif

t.cpp

#include "t.h"

T::T() {}

x.cpp

#include "a.h"

y.cpp的

#include "a.h"

3 个答案:

答案 0 :(得分:6)

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

瞧。它有效。

答案 1 :(得分:3)

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

根据你对S级和T级id的意图,你必须给他们一个独特的家。一个可能是将它们停放在main.cpp中。 main.cpp 添加

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

,在s.cpp中输入S的id,在t.cpp中输入T的id:

<强> 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并在那里定义它们。

答案 2 :(得分:1)

您可以使用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<T>由于内部链接而与另一个翻译单元中的{ name: 'name', index: 'name', width: 90, sorttype: "text", editrules: { required: true }} 不同,因此一个定义规则没有违反。