使用不同对象时模板专业化的多种定义


95

当我在不同的目标文件中使用专用模板时,链接时出现“多个定义”错误。我发现的唯一解决方案涉及使用“内联”功能,但似乎有些解决方法。如何不使用“ 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中的“内联”,则代码将编译并运行,但是对我而言,这似乎是某种“替代方法”:如果专用功能很大且已被多次使用怎么办?我会得到一个很大的二进制文件吗?还有其他方法吗?如果是,怎么办?如果没有,为什么?

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

谢谢


6
将实际的专用实现放入.cpp而非头文件中
Anycorn 2010年

Answers:


129

直观地讲,当您完全专门化某些东西时,它不再依赖于模板参数-因此,除非您进行专门化内联,否则您需要将其放入.cpp文件而不是.h中,否则您将违反正如大卫所说的一种定义规则。请注意,当您对模板进行部分专业化时,部分专业化仍然依赖于一个或多个模板参数,因此它们仍位于.h文件中。


嗯,我对它如何破坏ODR仍然有些困惑。因为您只定义一次完全专业化的模板。您可能在不同的对象文件中多次创建对象(即,在这种情况下,它是在other.c和main.c中实例化的),但是原始对象本身仅在一个文件中定义-在这种情况下hello.h
贾斯汀·梁

3
@JustinLiang:标头包含在两个单独的.c文件中-效果与您将其内容(包括完整的专业化内容)直接写入相关位置的文件的效果相同。一个定义规则(请参见en.wikipedia.org/wiki/One_Definition_Rule)说(除其他外):“在整个程序中,一个对象或非内联函数不能具有多个定义”。在这种情况下,函数模板的完全专业化本质上就像普通函数一样,因此除非是内联函数,否则它不能有多个定义。
Stuart Golodetz

嗯,我注意到当我们没有模板化的特殊化时,不会出现此错误。假设我们在头文件中定义了两个不同的函数,这些函数在类之外,它们仍可以在没有内联的情况下工作?例如:pastebin.com/raw.php?i=bRaiNC7M。我参加了该课程,并将其包含在两个文件中。这不会直接在两个文件中产生“就像您将内容写入内容一样的效果”,因此会出现多定义错误吗?
贾斯汀·梁

@Justin Liang,如果您的基于类的头文件代码包含在多个文件中,则它仍然会违反ODR,除非函数定义位于类的主体内。
haripkannan

因此,如果在我的静态成员定义之前添加了静态成员定义,template <typename T>那么它可能会进入标头,而如果不是,template<>那么可能不会?
紫罗兰色长颈鹿

49

关键字的主要作用inline是告诉编译器该符号将出现在多个对象文件中而不违反“一个定义规则”,而不是实际的内联,编译器可以决定执行或不执行。

您看到的问题是,没有内联,函数将在所有包含标头的转换单元中进行编译,这违反了ODR。添加inline那里是正确的方法。否则,您可以像声明其他任何功能一样,前向声明该专业化并在单个翻译单元中提供它。


22

您已经在标题(void Hello<T>::print_hello(T var))中明确实例化了一个模板。这将创建多个定义。您可以通过两种方式解决它:

1)使实例化内联。

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


其实,有哪些是把那些在一个没有名字的命名空间......这是类似于C具有静态第3方式
亚历克西斯维尔克

4
在这里无效。模板专业化必须与原始模板位于相同的名称空间中。
Edward Strange 2014年

0

下面是一些的C ++与这个问题有关的11个标准:

仅当使用内联说明符声明或定义为已删除时,函数模板的显式特化才是内联的,而与其功能模板是否为内联无关。[示例:

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

template <>内联void f <>(int){/ * ... /} // OK:内联template <> int g <>(int){/ ... * /} // OK:不内联—结束例子

因此,如果您在*.h文件中对模板做一些显式的(又称完整的)专业化,那么您仍然需要inline帮助您摆脱对ODR的侵犯。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.