wielokrotne definiowanie specjalizacji szablonów podczas korzystania z różnych obiektów

98

Kiedy używam specjalistycznego szablonu w różnych plikach obiektowych, podczas łączenia pojawia się błąd „wielokrotnej definicji”. Jedyne rozwiązanie, które znalazłem, polega na użyciu funkcji „inline”, ale wydaje się, że jest to jakieś obejście. Jak rozwiązać ten problem bez użycia słowa kluczowego „inline”? Jeśli to niemożliwe, dlaczego?

Oto przykładowy kod:

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

Wreszcie:

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

Jeśli odkomentuję „inline” wewnątrz hello.h, kod skompiluje się i uruchomi, ale wydaje mi się to pewnym „obejściem”: co jeśli wyspecjalizowana funkcja jest duża i używana wiele razy? Czy dostanę duży plik binarny? Czy jest inny sposób, aby to zrobić? Jeśli tak, w jaki sposób? Jeśli nie, dlaczego?

Próbowałem poszukać odpowiedzi, ale wszystko, co otrzymałem, to „użyj inline” bez dalszych wyjaśnień.

Dzięki

pzanoni
źródło
7
umieść rzeczywistą wyspecjalizowaną implementację w .cpp zamiast pliku nagłówkowego
Anycorn,

Odpowiedzi:

133

Intuicyjnie, kiedy w pełni specjalizujesz się w czymś, nie zależy to już od parametru szablonu - więc jeśli nie wprowadzisz specjalizacji w tekście, musisz umieścić ją w pliku .cpp zamiast .h lub w końcu naruszysz jedna zasada definicji, jak mówi David. Zwróć uwagę, że jeśli częściowo specjalizujesz się w szablonach, częściowe specjalizacje nadal zależą od jednego lub większej liczby parametrów szablonu, więc nadal znajdują się w pliku .h.

Stuart Golodetz
źródło
Hmmm nadal jestem trochę zdezorientowany, jak to łamie ODR. Ponieważ w pełni wyspecjalizowany szablon definiuje się tylko raz. Możesz tworzyć obiekt wiele razy w różnych plikach obiektowych (tj. W tym przypadku jest on tworzony w other.c i main.c), ale sam oryginalny obiekt jest zdefiniowany tylko w jednym pliku - w tym przypadku hello.h.
Justin Liang
3
@JustinLiang: Nagłówek jest zawarty w dwóch osobnych plikach .c - ma to taki sam efekt, jak gdybyś zapisał jego zawartość (w tym pełną specjalizację) bezpośrednio w plikach, w których jest zawarty w odpowiednich miejscach. Reguła jednej definicji (patrz en.wikipedia.org/wiki/One_Definition_Rule ) mówi (między innymi): „W całym programie obiekt lub funkcja nieliniowa nie może mieć więcej niż jednej definicji”. W tym przypadku pełna specjalizacja szablonu funkcji jest zasadniczo taka sama, jak normalna funkcja, więc jeśli nie jest wbudowana, nie może mieć więcej niż jednej definicji.
Stuart Golodetz
Hmmm, zauważyłem, że jeśli nie mamy specjalizacji opartej na szablonach, ten błąd się nie pojawi. Powiedzmy, że mieliśmy dwie różne funkcje, które zostały zdefiniowane w pliku nagłówkowym, poza klasą. Będą one nadal działać bez inline? Na przykład: pastebin.com/raw.php?i=bRaiNC7M . Wziąłem te zajęcia i umieściłem je w dwóch plikach. Czy nie miałoby to „takiego samego efektu, jak gdybyś zapisał zawartość” bezpośrednio w dwóch plikach, a zatem wystąpiłby błąd wielokrotnej definicji?
Justin Liang,
@Justin Liang, Twój kod nagłówka oparty na klasie nadal będzie naruszał ODR, jeśli zostanie dołączony do wielu plików, chyba że definicje funkcji znajdują się w treści klasy.
haripkannan
Tak więc, jeśli moja statyczna definicja elementu członkowskiego jest poprzedzona przez, template <typename T>to może trafić do nagłówka, a jeśli tak, template<>to może nie?
Violet Giraffe
50

Słowo kluczowe inlinedotyczy bardziej informowania kompilatora, że ​​symbol będzie obecny w więcej niż jednym pliku obiektowym bez naruszania reguły jednej definicji, niż rzeczywistego wstawiania, które kompilator może zdecydować, czy zrobić, lub nie.

Problem, który widzisz, polega na tym, że bez inline funkcja zostanie skompilowana we wszystkich jednostkach tłumaczeniowych, które zawierają nagłówek, co narusza ODR. Dodanie inlinetego jest właściwą drogą. W przeciwnym razie możesz zadeklarować specjalizację i podać ją w pojedynczej jednostce tłumaczeniowej, tak jak w przypadku każdej innej funkcji.

David Rodríguez - dribeas
źródło
22

Wyraźnie utworzyłeś wystąpienie szablonu w swoim header ( void Hello<T>::print_hello(T var)). Spowoduje to utworzenie wielu definicji. Możesz go rozwiązać na dwa sposoby:

1) Utwórz instancję w tekście.

2) Zadeklaruj instancję w nagłówku, a następnie zaimplementuj ją w cpp.

Edward Strange
źródło
Właściwie istnieje trzeci sposób, który polega na umieszczeniu tych w przestrzeni nazw bez nazw ... co jest podobne do statycznego w C.
Alexis Wilke,
4
To nie obowiązuje tutaj. Specjalizacja szablonu musi znajdować się w tej samej przestrzeni nazw, co oryginalny szablon.
Edward Strange,
0

Oto fragment standardu C ++ 11 związany z tym problemem:

Jawna specjalizacja szablonu funkcji jest wbudowana tylko wtedy, gdy jest zadeklarowana za pomocą specyfikatora wbudowanego lub zdefiniowana jako usunięta i niezależnie od tego, czy jej szablon funkcji jest wbudowany. [Przykład:

template void f (T) {/ * ... /} template inline T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: inline template <> int g <> (int) {/ ... * /} // OK: not inline - end przykład]

Więc jeśli stworzysz jakieś wyraźne (czyli pełne) specjalizacje szablonów w *.hpliku, nadal będziesz musiał inlinepomóc Ci pozbyć się naruszenia ODR .

Francis
źródło