[C++]Edition de lien pour template

Résolu/Fermé
lanny - 13 mars 2007 à 20:13
 lanny - 14 mars 2007 à 19:13
Bonjour,

à la compilation d'un programme utilisant une classe générique, j'ai obtenu des erreurs du à l'édition de lien.
J'ai regardé sur le net, qu'il fallait créer un fichier .tcc qui contiendrait les templates.
Le soucis, c'est que je n'arrive pas très bien à faire ça.
Est-ce que quelqu'un pourrait m'indiquer un exemple pour que je puisse l'adapter dans mon cas.
Un fichier .cc, .hh et .tcc

Merci
A voir également:

3 réponses

Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
14 mars 2007 à 08:34
Salut.
Il faut TOUT mettre dans le même fichier.
en template, comme tu compile à chaque fichier, il faut mettre déclaration et implémentation dans le .hpp
et ton erreur viens surtout du fait que tu fait
template <typename T>
Exemple<T>::Exemple(){
std::cout << "ceci est un exemple" << std::endl;
}
dans le .c, alors qu'il est déjà défini dans le .tpp inclu dans le .hh
3
mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 7 749
14 mars 2007 à 16:28
Ca n'as aucun sens de créer un .o d'un fichier contenant des fonctions templates, c'est ce que je t'ai expliqué dans le premier post, car tu ne sais pas pour quels types la classe template va être instanciée. Il faut donc mettre le code des fonction template dans le header (.hpp). Si tu mets les fonctions templates dans un fichier séparé, celui-ci doit être inclu de la même façon qu'un .hpp.

Je t'invite à repartir des exemples que je t'ai donné, et de prendre le temps de comprendre ce que j'ai baragouiné la fois précédente.

Bonne chance
2
Salut,

j'y suis arrivé.
Ce n'étais pas aussi compliqué qu'il me semblait.
0
mamiemando Messages postés 33079 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 23 avril 2024 7 749
14 mars 2007 à 01:46
Introduction

Concrètement un fichier tcc peut être vu comme un .h car les classes templates ne peuvent être compilées directement, car tu ne sais pas pour quel(s) type(s) elle sera utilisée (et dans le cas générique ça peut être n'importe quel type !).
Ce qu'il faut bien voir c'est que concrètement pour une classe template donnée, celle-ci sera compilée pour chaque type;
#include <vector> // dans ce header se trouve la classe template std::vector

int main(){
  std::vector<int> v; // => compilation de std::vector<int>
  std::vector<double> w; // => compilation de std::vector<double>
  return 0;
}


Personnellement, je mets simplement les classes et les fonctions templates dans les .hpp mais tu peux les mettre à part dans un tcc.

Contrairement aux fonctions classiques implémentées dans le .hpp, tu n'as pas besoin d'utiliser le mot clé inline (qui évite les multidéfinitions des fonctions implémentées dans le .hpp si celui-ci est inclu à plusieurs endroits). Dans l'exemple ci dessous je détaille à quoi sert le mot clé inline.

Un exemple

plop.hpp
#ifndef PLOP  //verrou pour n'inclure ce fichier qu'une fois... par .o !
#define PLOP

#include <iostream>
#include <vector>

// 1er exemple : une fonction template

// Recherche le maximum dans un vector
template <typename T>
std::pair<T,bool> max(const std::vector<T> & v){
  // je vérifie que le vector est non vide pour pouvoir utiliser v[0] ensuite
  if (v.empty()) return std::make_pair(res,false);

  T res = v[0];
  typename std::vector<T>::const_iterator vit = v.begin(), vend = v.end();
  // Le typename est important car on accède (::) à un champ (const_iterator)
  // de la classe std::vector<T> qui dépend d'un paramètre template.


  // je parcours le vector pour trouver le maximum
  for(;vit!=vend;++vit){
    if(*vit > res) res = *vit;
  }
  return std::make_pair(res,true);
}

// 2eme exemple : une classe/structure template

template <typename T>
struct ma_classe(){
  T t;

  ma_classe(){}

  void print() const{
    std::cout << t << std::endl;
  }
};

// 3eme exemple : une méthode template

class plop{
  protected
  int x;
  
  // Le constructeur
  plop(int x0=0):x(x0){};

  // Cette méthode est implémentée dans le .hpp il faut donc la mettre inline
  // En général on ne met que des méthodes "triviales" et courtes en inline car
  // concrètement le inline recopie le code de la fonction inline à chaque appel
  // de cette méthode. Si tu ne mets pas le inline et que ce fichier est inclu
  // par a.cpp et b.cpp, le code de la fonction serait présent dans a.o et b.o,
  // et au moment de linker tu aurais une multidéfinition de cette méthode.
  inline int get_x() const{
    return x;
  }

  // Méthode template
  template <Tstream> 
  Tstream & operator << (Tstream & out) const{
    out << x;
    return out;
  }

  // Méthode déclarée dans le .hpp, implémentées dans le .cpp
  void set_x(int x0);
};

// Comme toute fonction déclarée et implémentée dans un .hpp, je dois mettre
// un inline car sinon j'aurais une multidéfinition de cette fonction si plop.hpp
// est inclu à plusieurs endroits
inline void ecrire_tapir(){
  std::cout << "tapir" << std::endl;
}

#endif

plop.cpp
void plop::set_x(int x0){
  x = x0;
}


Concrètement que se passe-t'il ?

Pour insister sur le problème des inline si tu considères les fichiers :
- main.cpp, a.cpp, b.cpp
- a.hpp, (inclu par a.cpp et main.cpp)
- b.hpp, (inclu par b.cpp et main.cpp)
- plop.hpp (inclu par a.cpp et b.cpp)

Dans a.o tu auras compilé les codes des fonctions non template de a.hpp et plop.hpp.
Dans b.o tu auras compilé les codes des fonctions non template de b.hpp et plop.hpp.
Dans main.o tu auras compilé les codes des fonctions non template de a.hpp, b.hpp et plop.hpp.

Ne perds pas de vue que les fonctions inline n'ont pas de symbole proprement dit car elles sont directement remplacée par leur code à chaque appel, et donc tu ne pourras jamais avoir de multidéfinition sur ces fonctions là.

Au moment de construire l'exécutable, tu vas linker a.o, b.o et main.o. Si certaines fonctions apparaissent dans plusieurs .o ça fait une multidéfinition (typiquement une fonction non template et non inline définie dans a.hpp (respectivement b.hpp) car celle-ci sera compilée dans a.o (respectivement b.cpp) et main.o. Et ce malgré les verrous que tu as pu mettre dans a.hpp et b.hpp !

Si je n'ai pas été claire n'hésite pas à me dire ce que tu n'as pas compris.

Bonne chance
0
Bonsoir,

désolé mais j'ai du mal à comprendre comment ça se passe.
J'ai tenté de faire avec un exemple basique mais ça ne fonctionne pas
A la compilation de l'exemple, j'obtiens ce message :
g++ -o exemples.o -c exemples.cc -W -Wall -g
exemples.cc:7: erreur: redefinition of ‘Exemple<T>::Exemple()’
exemples.tpp:4: erreur: ‘Exemple<T>::Exemple()’ previously declared here
make: *** [exemples.o] Erreur 1


Est-ce que tu pourrais m'indiquer dans l'exemple que j'ai écrit comment se corrige l'erreur

Merci

Voici les différents fichier et le Makefile

// exemple.hh

#ifndef EXEMPLE_HH
#define EXEMPLE_HH

template <typename T>
class Exemple
{
public:
    Exemple();
};

#include "exemples.tpp"
#endif

// exemple.tpp

template <typename T>
Exemple<T>::Exemple()
{
}

#include <iostream>

#include "exemples.hh"


template <typename T>
Exemple<T>::Exemple(){
std::cout << "ceci est un exemple" << std::endl;
}

int main(){
Exemple<int> ex;
return 0;
}

CC=g++
CFLAGS=-W -Wall -g
LDFLAGS=-W -Wall -g
EXEC=exo

all: $(EXEC)

exo: exemples.o
	$(CC) -o exo exemples.o $(LDFLAGS)

exemples.o: exemples.cc
	$(CC) -o exemples.o -c exemples.cc $(CFLAGS)

clean:
	rm -rf *.o

mrproper: clean
	rm -rf $(EXEC)
0