множественное определение специализации шаблона при использовании разных объектов

96

Когда я использую специализированный шаблон в разных объектных файлах, я получаю ошибку «множественное определение» при связывании. Единственное решение, которое я нашел, включает использование «встроенной» функции, но это похоже на обходной путь. Как мне решить эту проблему без использования ключевого слова "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

Если я раскомментирую «inline» внутри hello.h, код будет скомпилирован и запущен, но мне это кажется своего рода «обходным путем»: что, если специализированная функция большая и используется много раз? Получу ли я большой двоичный файл? Есть ли другой способ сделать это? Если да, то как? Если нет, то почему?

Я попытался найти ответы, но все, что я получил, это «использовать встроенный» без каких-либо дополнительных объяснений.

Спасибо

пзанони
источник
7
поместите фактическую специализированную реализацию в .cpp, а не в файл заголовка
Anycorn

Ответы:

130

Интуитивно понятно, что когда вы что-то полностью специализируете, это больше не зависит от параметра шаблона - поэтому, если вы не сделаете специализацию встроенной, вам нужно поместить ее в файл .cpp вместо .h, иначе вы в конечном итоге нарушите одно правило определения, как говорит Дэвид. Обратите внимание, что когда вы частично специализируете шаблоны, частичные специализации все еще зависят от одного или нескольких параметров шаблона, поэтому они по-прежнему помещаются в файл .h.

Стюарт Голодец
источник
Хммм, я до сих пор не понимаю, как это нарушает ODR. Потому что вы определяете полностью специализированный шаблон только один раз. Вы можете создавать объект несколько раз в разных объектных файлах (т. Е. В этом случае он создается в other.c и main.c), но сам исходный объект определен только в одном файле - в этом случае hello.h.
Джастин Лян
3
@JustinLiang: заголовок включается в два отдельных файла .c - это имеет тот же эффект, как если бы вы записали его содержимое (включая полную специализацию) непосредственно в файлы, в которые он включен, в соответствующих местах. Правило одного определения (см. En.wikipedia.org/wiki/One_Definition_Rule ) гласит (среди прочего): «Во всей программе объект или не встроенная функция не может иметь более одного определения». В этом случае полная специализация шаблона функции, по сути, аналогична обычной функции, поэтому, если она не встроена, у нее не может быть более одного определения.
Стюарт Голодец
Хммм, я заметил, что когда у нас нет шаблонной специализации, эта ошибка не появится. Допустим, у нас есть две разные функции, которые были определены в файле заголовка, вне класса, они все равно будут работать без встроенного? Например: pastebin.com/raw.php?i=bRaiNC7M . Я взял этот класс и включил его в два файла. Разве это не имело бы «такого же эффекта, как если бы вы записали содержимое» непосредственно в два файла, и, следовательно, возникнет множественная ошибка определения?
Джастин Лян
@Justin Liang, ваш код заголовка на основе класса по-прежнему будет нарушать ODR, если он включен в несколько файлов, если только определения функций не находятся внутри тела класса.
haripkannan
Итак, если моему статическому определению члена предшествует, template <typename T>тогда оно может перейти в заголовок, а если оно, template<>то нет?
Violet Giraffe
49

Ключевое слово inlineбольше сообщает компилятору, что символ будет присутствовать более чем в одном объектном файле без нарушения правила единого определения, чем о фактическом встраивании, которое компилятор может решить делать или не делать.

Проблема, которую вы видите, заключается в том, что без встроенного кода функция будет скомпилирована во всех единицах перевода, которые включают заголовок, что нарушает ODR. Добавление inlineтуда - правильный путь. В противном случае вы можете напрямую объявить специализацию и предоставить ее в одной единице перевода, как и с любой другой функцией.

Давид Родригес - дрибеас
источник
22

Вы явно создали экземпляр шаблона в своем header ( void Hello<T>::print_hello(T var)). Это создаст несколько определений. Решить ее можно двумя способами:

1) Сделайте свой экземпляр встроенным.

2) Объявите экземпляр в заголовке, а затем реализуйте его в cpp.

Эдвард Стрэндж
источник
На самом деле есть третий способ - поместить их в пространство имен no name ... который похож на статический в C.
Alexis Wilke
4
Здесь это не действует. Специализация шаблона должна находиться в том же пространстве имен, что и исходный шаблон.
Эдвард Стрэндж
0

Вот часть стандарта C ++ 11, связанного с этой проблемой:

Явная специализация шаблона функции является встроенной, только если она объявлена ​​с помощью встроенного спецификатора или определена как удаленная, и независимо от того, является ли ее шаблон функции встроенным. [ Пример:

template void f (T) {/ * ... /} шаблон inline T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // ОК: встроенный шаблон <> int g <> (int) {/ ... * /} // ОК: не встроенный - конец пример ]

Поэтому, если вы сделаете некоторые явные (или полные) специализации шаблонов в *.hфайле, вам все равно нужно inlineбудет помочь вам избавиться от нарушения ODR .

Фрэнсис
источник