définition multiple de la spécialisation des modèles lors de l'utilisation de différents objets

95

Lorsque j'utilise un modèle spécialisé dans différents fichiers objets, j'obtiens une erreur de «définition multiple» lors de la liaison. La seule solution que j'ai trouvée consiste à utiliser la fonction "en ligne", mais cela semble être une solution de contournement. Comment résoudre ce problème sans utiliser le mot clé "inline"? Si ce n'est pas possible, pourquoi?

Voici l'exemple de code:

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

Finalement:

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

Si je décommente le "inline" dans hello.h, le code se compilera et s'exécutera, mais cela me semble être une sorte de "contournement": et si la fonction spécialisée est volumineuse et utilisée plusieurs fois? Vais-je avoir un gros binaire? Y a-t-un autre moyen de faire ça? Si oui, comment? Si non, pourquoi?

J'ai essayé de chercher des réponses, mais tout ce que j'ai obtenu était "utiliser en ligne" sans aucune autre explication.

Merci

pzanoni
la source
6
mettre l'implémentation spécialisée réelle dans .cpp plutôt que dans le fichier d'en-tête
Anycorn

Réponses:

129

Intuitivement, lorsque vous spécialisez complètement quelque chose, cela ne dépend plus d'un paramètre de modèle - donc à moins que vous ne fassiez la spécialisation en ligne, vous devez le mettre dans un fichier .cpp au lieu d'un .h ou vous finissez par violer le une règle de définition comme le dit David. Notez que lorsque vous spécialisez partiellement les modèles, les spécialisations partielles dépendent toujours d'un ou plusieurs paramètres de modèle, elles sont donc toujours placées dans un fichier .h.

Stuart Golodetz
la source
Hmmm, je suis encore un peu confus sur la façon dont il brise l'ODR. Parce que vous ne définissez qu'une seule fois le modèle entièrement spécialisé. Vous pouvez créer l'objet plusieurs fois dans différents fichiers objets (c'est-à-dire, dans ce cas, il est instancié dans other.c et main.c) mais l'objet d'origine lui-même n'est défini que dans un seul fichier - dans ce cas hello.h.
Justin Liang
3
@JustinLiang: L'en-tête est inclus dans deux fichiers .c séparés - cela a le même effet que si vous aviez écrit son contenu (y compris la spécialisation complète) directement dans les fichiers dans lesquels il est inclus aux endroits appropriés. La règle One Definition (voir en.wikipedia.org/wiki/One_Definition_Rule ) dit (entre autres): "Dans tout le programme, un objet ou une fonction non en ligne ne peut pas avoir plus d'une définition". Dans ce cas, la spécialisation complète du modèle de fonction est essentiellement comme une fonction normale, donc à moins qu'elle ne soit en ligne, elle ne peut pas avoir plus d'une définition.
Stuart Golodetz
Hmmm, j'ai remarqué que lorsque nous n'avons pas de spécialisation basée sur un modèle, cette erreur n'apparaîtra pas. Disons que nous avons eu deux fonctions différentes qui ont été définies dans le fichier d'en-tête, en dehors de la classe, elles fonctionneront toujours sans l'inline? Par exemple: pastebin.com/raw.php?i=bRaiNC7M . J'ai suivi ce cours et l'ai inclus dans deux fichiers. Cela n'aurait-il pas "le même effet que si vous aviez écrit le contenu" directement dans les deux fichiers et qu'il y aurait donc une erreur de définition multiple?
Justin Liang
@Justin Liang, votre code d'en-tête basé sur la classe enfreindra toujours l'ODR s'il est inclus dans plusieurs fichiers, à moins que les définitions de fonction ne se trouvent dans le corps de la classe.
haripkannan
Donc, si ma définition de membre statique est précédée de, template <typename T>elle peut entrer dans un en-tête, et si c'est le cas, ce n'est template<>peut-être pas le cas?
Violet Giraffe
49

Le mot inline- clé consiste davantage à indiquer au compilateur que le symbole sera présent dans plusieurs fichiers objets sans violer la règle d'une définition qu'à une insertion réelle, ce que le compilateur peut décider de faire ou de ne pas faire.

Le problème que vous voyez est que sans l'inline, la fonction sera compilée dans toutes les unités de traduction qui incluent l'en-tête, violant l'ODR. L'ajout inlineest la bonne voie à suivre. Sinon, vous pouvez transmettre la déclaration de spécialisation et la fournir dans une seule unité de traduction, comme vous le feriez avec n'importe quelle autre fonction.

David Rodríguez - Dribeas
la source
22

Vous avez explicitement instancié un modèle dans votre en-tête (void Hello<T>::print_hello(T var) ). Cela créera plusieurs définitions. Vous pouvez le résoudre de deux manières:

1) Créez votre instanciation en ligne.

2) Déclarez l'instanciation dans un en-tête, puis implémentez-la dans un cpp.

Edward étrange
la source
En fait, il existe une troisième méthode qui consiste à les mettre dans un espace de noms sans nom ... ce qui est similaire à avoir statique dans C.
Alexis Wilke
4
Ce n'est pas valable ici. Une spécialisation de modèle doit se trouver dans le même espace de noms que le modèle d'origine.
Edward Strange
0

Voici une partie de la norme C ++ 11 liée à ce problème:

Une spécialisation explicite d'un modèle de fonction est en ligne uniquement si elle est déclarée avec le spécificateur en ligne ou définie comme supprimée, et indépendamment du fait que son modèle de fonction est en ligne. [ Exemple:

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

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

Donc, si vous effectuez des spécialisations explicites (c'est-à-dire complètes) de modèles dans un *.hfichier, vous devrez toujours inlinevous aider à vous débarrasser de la violation de l' ODR .

Francis
la source