Mehrfachdefinition der Vorlagenspezialisierung bei Verwendung verschiedener Objekte

95

Wenn ich eine spezielle Vorlage in verschiedenen Objektdateien verwende, wird beim Verknüpfen ein Fehler "Mehrfachdefinition" angezeigt. Die einzige Lösung, die ich gefunden habe, ist die Verwendung der "Inline" -Funktion, aber es scheint nur eine Problemumgehung zu sein. Wie löse ich das, ohne das Schlüsselwort "inline" zu verwenden? Wenn das nicht möglich ist, warum?

Hier ist der Beispielcode:

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

Schließlich:

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

Wenn ich das "Inline" in hello.h auskommentiere, wird der Code kompiliert und ausgeführt, aber das scheint mir nur eine Art "Problemumgehung" zu sein: Was ist, wenn die spezialisierte Funktion groß ist und oft verwendet wird? Bekomme ich eine große Binärdatei? Gibt es eine andere Möglichkeit, dies zu tun? Wenn ja, wie? Wenn nicht, warum?

Ich habe versucht, nach Antworten zu suchen, aber alles, was ich bekam, war "Inline verwenden" ohne weitere Erklärung.

Vielen Dank

pzanoni
quelle
6
Setzen Sie die eigentliche spezialisierte Implementierung in die CPP-Datei und nicht in die Header-Datei
Anycorn,

Antworten:

129

Wenn Sie etwas vollständig spezialisieren, hängt es intuitiv nicht mehr von einem Vorlagenparameter ab. Wenn Sie die Spezialisierung nicht inline durchführen, müssen Sie sie in eine CPP-Datei anstatt in eine .h-Datei einfügen, oder Sie verletzen am Ende die eine Definitionsregel, wie David sagt. Beachten Sie, dass beim teilweisen Spezialisieren von Vorlagen die teilweisen Spezialisierungen immer noch von einem oder mehreren Vorlagenparametern abhängen, sodass sie weiterhin in einer .h-Datei gespeichert werden.

Stuart Golodetz
quelle
Hmmm, ich bin immer noch ein bisschen verwirrt darüber, wie es die ODR bricht. Weil Sie die vollständig spezialisierte Vorlage nur einmal definieren. Möglicherweise erstellen Sie das Objekt mehrmals in verschiedenen Objektdateien (dh in diesem Fall wird es in other.c und main.c instanziiert), aber das ursprüngliche Objekt selbst wird nur in einer Datei definiert - in diesem Fall hello.h.
Justin Liang
3
@JustinLiang: Der Header ist in zwei separaten .c-Dateien enthalten. Dies hat den gleichen Effekt, als hätten Sie seinen Inhalt (einschließlich der vollständigen Spezialisierung) direkt in die Dateien geschrieben, in denen er an den entsprechenden Stellen enthalten ist. Die One Definition Rule (siehe en.wikipedia.org/wiki/One_Definition_Rule ) besagt (unter anderem): "Im gesamten Programm kann ein Objekt oder eine Nicht-Inline-Funktion nicht mehr als eine Definition haben." In diesem Fall entspricht die vollständige Spezialisierung der Funktionsvorlage im Wesentlichen einer normalen Funktion. Wenn sie nicht inline ist, kann sie nicht mehr als eine Definition haben.
Stuart Golodetz
Hmmm, ich habe festgestellt, dass dieser Fehler nicht auftritt, wenn wir keine Spezialisierung mit Vorlagen haben. Angenommen, wir hatten zwei verschiedene Funktionen, die in der Header-Datei außerhalb der Klasse definiert wurden. Sie funktionieren weiterhin ohne Inline. Zum Beispiel: pastebin.com/raw.php?i=bRaiNC7M . Ich nahm diese Klasse und fügte sie in zwei Dateien ein. Hätte dies nicht "den gleichen Effekt, als hätten Sie den Inhalt direkt in die beiden Dateien geschrieben", und es würde daher ein Mehrfachdefinitionsfehler auftreten?
Justin Liang
@Justin Liang, Ihr klassenbasierter Header-Code verletzt weiterhin die ODR, wenn er in mehreren Dateien enthalten ist, es sei denn, die Funktionsdefinitionen befinden sich im Hauptteil der Klasse.
Haripkannan
Wenn also vor meiner statischen Elementdefinition template <typename T>ein Header template<>angezeigt wird, wird dies möglicherweise nicht der Fall sein.
Violette Giraffe
49

Bei dem Schlüsselwort inlinegeht es mehr darum, dem Compiler mitzuteilen, dass das Symbol in mehr als einer Objektdatei vorhanden ist, ohne die One Definition-Regel zu verletzen, als um das tatsächliche Inlining, das der Compiler entscheiden kann oder nicht.

Das Problem, das Sie sehen, ist, dass die Funktion ohne Inline in allen Übersetzungseinheiten kompiliert wird, die den Header enthalten, was gegen die ODR verstößt. Hinzu inlinekommt der richtige Weg. Andernfalls können Sie die Spezialisierung weiterleiten und in einer einzigen Übersetzungseinheit bereitstellen, wie Sie es bei jeder anderen Funktion tun würden.

David Rodríguez - Dribeas
quelle
22

Sie haben eine Vorlage in Ihrem Header explizit instanziiert (void Hello<T>::print_hello(T var) ) . Dadurch werden mehrere Definitionen erstellt. Sie können es auf zwei Arten lösen:

1) Machen Sie Ihre Instanziierung inline.

2) Deklarieren Sie die Instanziierung in einem Header und implementieren Sie sie dann in einem CPP.

Edward Strange
quelle
Tatsächlich gibt es eine dritte Möglichkeit, diese in einen Namespace ohne Namen zu setzen ... ähnlich wie statisch in C.
Alexis Wilke
4
Das ist hier nicht gültig. Eine Vorlagenspezialisierung muss sich im selben Namespace wie die ursprüngliche Vorlage befinden.
Edward Strange
0

Hier finden Sie einige Stück von C ++ 11 - Standard zu diesem Problem:

Eine explizite Spezialisierung einer Funktionsvorlage ist nur dann inline, wenn sie mit dem Inline-Bezeichner deklariert oder als gelöscht definiert wurde und unabhängig davon, ob ihre Funktionsvorlage inline ist. [Beispiel:

Vorlage ungültig f (T) {/ * ... /} Vorlage Inline T g (T) {/ ... * /}

template <> inline void f <> (int) {/ * ... /} // OK: Inline-Vorlage <> int g <> (int) {/ ... * /} // OK: nicht inline - end Beispiel]

Wenn Sie also einige explizite (auch als vollständige) Spezialisierungen von Vorlagen in *.hDateien vornehmen , müssen Sie trotzdem inlinehelfen, die Verletzung von ODR zu beseitigen .

Francis
quelle