【问题标题】:multiple definition of template specialization when using different objects使用不同对象时模板特化的多重定义
【发布时间】:2011-05-25 15:27:23
【问题描述】:

当我在不同的目标文件中使用专用模板时,链接时出现“多重定义”错误。我找到的唯一解决方案是使用“内联”函数,但这似乎是一些解决方法。如何在不使用“inline”关键字的情况下解决这个问题?如果这不可能,为什么?

示例代码如下:

paulo@aeris:~/teste/cpp/redef$ cat hello.h 
#ifndef TEMPLATE_H
#define TEMPLATE_H

#include <iostream>

template <class T>
class Hello
{
public:
    void print_hello(T var);
};

template <class T>
void Hello<T>::print_hello(T var)
{
    std::cout << "Hello generic function " << var << "\n";
}

template <> //inline
void Hello<int>::print_hello(int var)
{
    std::cout << "Hello specialized function " << var << "\n";
}

#endif

paulo@aeris:~/teste/cpp/redef$ cat other.h 
#include <iostream>

void other_func();

paulo@aeris:~/teste/cpp/redef$ cat other.c 
#include "other.h"

#include "hello.h"

void other_func()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);
}

paulo@aeris:~/teste/cpp/redef$ cat main.c 
#include "hello.h"

#include "other.h"

int main()
{
    Hello<char> hc;
    Hello<int> hi;

    hc.print_hello('a');
    hi.print_hello(1);

    other_func();

    return 0;
}

paulo@aeris:~/teste/cpp/redef$ cat Makefile
all:
    g++ -c other.c -o other.o -Wall -Wextra
    g++ main.c other.o -o main -Wall -Wextra

最后:

paulo@aeris:~/teste/cpp/redef$ make
g++ -c other.c -o other.o -Wall -Wextra
g++ main.c other.o -o main -Wall -Wextra
other.o: In function `Hello<int>::print_hello(int)':
other.c:(.text+0x0): multiple definition of `Hello<int>::print_hello(int)'
/tmp/cc0dZS9l.o:main.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
make: ** [all] Erro 1

如果我取消注释 hello.h 中的“内联”,代码将编译并运行,但这对我来说似乎是某种“解决方法”:如果专用函数很大并且多次使用怎么办?我会得到一个大的二进制文件吗?有没有其他方法可以做到这一点?如果是,如何?如果不是,为什么?

我试图寻找答案,但我得到的只是“使用内联”,没有任何进一步的解释。

谢谢

【问题讨论】:

  • 将实际的专用实现放入.cpp而不是头文件

标签: c++ templates


【解决方案1】:

直观地说,当你完全特化某些东西时,它不再依赖于模板参数——所以除非你内联特化,否则你需要把它放在一个 .cpp 文件而不是一个 .h 或者你结束正如大卫所说,违反了单一定义规则。请注意,当您对模板进行部分特化时,部分特化仍然依赖于一个或多个模板参数,因此它们仍位于 .h 文件中。

【讨论】:

  • 嗯,我仍然对它如何破坏 ODR 感到有些困惑。因为您只定义了一次完全专业化的模板。您可能在不同的对象文件中多次创建对象(即,在这种情况下,它在 other.c 和 main.c 中实例化)但原始对象本身仅在一个文件中定义 - 在这种情况下 hello.h.
  • @JustinLiang:标头包含在两个单独的 .c 文件中 - 这与您将其内容(包括完整的专业化)直接写入包含它的文件中的效果相同相关的地方。单一定义规则(参见en.wikipedia.org/wiki/One_Definition_Rule)说(除其他外):“在整个程序中,一个对象或非内联函数不能有多个定义”。在这种情况下,函数模板的完全特化本质上就像一个普通函数,所以除非它是内联的,否则它不能有多个定义。
  • 嗯,我注意到当我们没有模板化专业化时,不会出现此错误。假设我们在头文件中定义了两个不同的函数,在类之外,它们在没有内联的情况下仍然可以工作吗?例如:pastebin.com/raw.php?i=bRaiNC7M。我参加了该课程并将其包含在两个文件中。这会不会“与将内容直接写入两个文件具有相同的效果”,因此会出现多重定义错误?
  • @Justin Liang,如果包含在多个文件中,您的基于类的标头代码仍然会违反 ODR,除非函数定义在类的主体内。
  • 那么,如果我的静态成员定义前面有template &lt;typename T&gt;,那么它可能会进入标题,如果它是template&lt;&gt;,那么它可能不会?
【解决方案2】:

关键字inline 更多的是告诉编译器该符号将出现在多个目标文件中而不违反单一定义规则,而不是关于实际内联,编译器可以决定执行或不执行。

您看到的问题是,如果没有内联,该函数将在包含标头的所有翻译单元中编译,这违反了 ODR。添加inline 是正确的方法。否则,您可以转发声明特化并在单个翻译单元中提供它,就像您对任何其他函数所做的那样。

【讨论】:

    【解决方案3】:

    您已在标头 (void Hello&lt;T&gt;::print_hello(T var)) 中显式实例化模板。这将创建多个定义。可以通过两种方式解决:

    1) 使您的实例化内联。

    2) 在标头中声明实例化,然后在 cpp 中实现。

    【讨论】:

    • 其实还有第三种方法,就是把它们放在一个没有名字的命名空间中......这类似于在 C 中使用静态。
    • 这里无效。模板特化需要与原始模板在同一个命名空间中。
    • 此外,标题中未命名的命名空间本身就是违反单一定义规则的来源,因此 CppCoreGuidelines 建议不要这样做:isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rs-unnamed
    【解决方案4】:

    这里是一些与此问题相关的 C++11 标准的 piece

    函数模板的显式特化只有在它 使用 inline 说明符声明或定义为已删除,并且 与其函数模板是否内联无关。 [ 例子:

    template void f(T) { /* ... / } 模板内联 T g(T) { / ... */ }

    template inline void f(int) { /* ... / } // OK: inline template int g(int) { / ... */ } // OK:不是 inline — 结束示例]

    因此,如果您在*.h 文件中对模板进行了一些显式(又名完整)特化,那么您仍然需要inline 来帮助您摆脱对ODR 的违反。 p>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-08
      • 1970-01-01
      相关资源
      最近更新 更多