Question

Un aspect de C ++ qui me frustre périodiquement est de décider où les modèles correspondent entre les fichiers d'en-tête (décrivant traditionnellement l'interface) et les fichiers implemention (.cpp). Les modèles ont souvent besoin d'aller dans l'en-tête, exposant la mise en œuvre et en tirant parfois des en-têtes supplémentaires qui, auparavant, ne devaient être inclus dans le fichier .cpp. J'ai rencontré ce problème encore une fois récemment, et un exemple simplifié de celui-ci est indiqué ci-dessous.

#include <iostream> // for ~Counter() and countAndPrint()

class Counter
{
  unsigned int count_;
public:
  Counter() : count_(0) {}
  virtual ~Counter();

  template<class T>
  void
  countAndPrint(const T&a);
};

Counter::~Counter() {
    std::cout << "total count=" << count_ << "\n";
}

template<class T>
void
Counter::countAndPrint(const T&a) {
  ++count_;
  std::cout << "counted: "<< a << "\n";
}

// Simple example class to use with Counter::countAndPrint
class IntPair {
  int a_;
  int b_;
public:
  IntPair(int a, int b) : a_(a), b_(b) {}
  friend std::ostream &
  operator<<(std::ostream &o, const IntPair &ip) {
    return o << "(" << ip.a_ << "," << ip.b_ << ")";
  }
};

int main() {
  Counter ex;
  int i = 5;
  ex.countAndPrint(i);
  double d=3.2;
  ex.countAndPrint(d);
  IntPair ip(2,4);
  ex.countAndPrint(ip);
}

Notez que j'ai l'intention d'utiliser ma classe réelle en tant que classe de base, d'où le destructeur virtuel; Je doute qu'il importe, mais je l'ai laissé en contre juste au cas où. Le signal de sortie résultant de ce qui précède est

counted: 5
counted: 3.2
counted: (2,4)
total count=3

La déclaration de classe de Counter pourrait tous aller dans un fichier d'en-tête (par exemple, counter.h). Je peux mettre la mise en œuvre de la dtor, qui exige iostream, en counter.cpp. Mais ce qu'il faut faire pour le modèle fonction membre countAndPrint(), qui utilise également iostream? Il ne sert à rien counter.cpp car il doit être instancié en dehors du counter.o compilé. Mais le mettre dans des moyens de counter.h que tout, y compris counter.h aussi à son tour comprend iostream, qui semble tout simplement faux (et je reconnais que je peux juste avoir à surmonter cette aversion). Je pourrais aussi mettre le code du modèle dans un fichier séparé (counter.t?), Mais ce serait un peu surprenant à d'autres utilisateurs du code. Lakos ne va pas vraiment dans cette autant que je voudrais, et C ++ FAQ ne va pas dans le meilleur entraine toi. Donc ce que je suis après est:

  1. sont-il des alternatives pour diviser le code à ceux que je l'ai suggéré?
  2. dans la pratique, ce qui fonctionne le mieux?
Était-ce utile?

La solution

Une règle de base (la raison de ce qui devrait être clair).

  • Modèles de membres privés doit être défini dans le fichier .cpp (à moins qu'ils ne doivent être appelable par des amis de votre modèle de classe).
  • Modèles membres non-privé doit être défini dans les en-têtes, à moins qu'ils ne soient explicitement instancié.

Vous pouvez souvent éviter d'avoir à inclure un bon nombre d'en-têtes en faisant en noms soient dépendants, ce qui retarde recherche et / ou la détermination de leur signification. De cette façon, vous avez besoin d'un ensemble complet d'en-têtes seulement au point de instanciation. À titre d'exemple

#include <iosfwd> // suffices

class Counter
{
  unsigned int count_;
public:
  Counter() : count_(0) {}
  virtual ~Counter();

  // in the .cpp file, this returns std::cout
  std::ostream &getcout();

  // makes a type artificially dependent
  template<typename T, typename> struct ignore { typedef T type; };

  template<class T>
  void countAndPrint(const T&a) {
    typename ignore<std::ostream, T>::type &cout = getcout();
    cout << count_;
  }
};

est ce que j'ai utilisé pour la mise en œuvre d'un modèle de visiteur qui utilise CRTP. Il ressemblait à ceci d'abord

template<typename Derived>
struct Visitor {
  Derived *getd() { return static_cast<Derived*>(this); }
  void visit(Stmt *s) {
    switch(s->getKind()) {
      case IfStmtKind: {
        getd()->visitStmt(static_cast<IfStmt*>(s));
        break;
      }
      case WhileStmtKind: {
        getd()->visitStmt(static_cast<WhileStmt*>(s));
        break;
      }
      // ...
    }
  }
};

Il faudra les en-têtes de toutes les classes d'instruction en raison de ces moulages statiques. J'ai donc fait les types soient dépendants, et je ne ai besoin des déclarations avant

template<typename T, typename> struct ignore { typedef T type; };

template<typename Derived>
struct Visitor {
  Derived *getd() { return static_cast<Derived*>(this); }
  void visit(Stmt *s) {
    typename ignore<Stmt, Derived>::type *sd = s;
    switch(s->getKind()) {
      case IfStmtKind: {
        getd()->visitStmt(static_cast<IfStmt*>(sd));
        break;
      }
      case WhileStmtKind: {
        getd()->visitStmt(static_cast<WhileStmt*>(sd));
        break;
      }
      // ...
    }
  }
};

Autres conseils

Le Google Style Guide suggère de mettre le code du modèle dans un " fichier contre-inl.h ». Si vous voulez être très prudent au sujet de votre comprend, qui pourrait être la meilleure façon.

Toutefois, les clients obtenir un en-tête de iostream inclus par « accident » est probablement un petit prix à payer pour avoir tout le code de votre classe dans un lieu à simple logique moins si vous ne disposez d'un seul modèle de fonction membre.

Pratiquement vos seules options sont à placer tout le code de modèle dans un en-tête ou à placer le code du modèle dans un fichier .tcc et comprennent ce fichier à la fin de votre tête .

De plus, si possible, vous devriez essayer d'éviter #includeing <iostream> les en-têtes, parce que cela a un péage important sur la compilation. En-têtes sont souvent #included par plusieurs fichiers de mise en œuvre, après tout. Le seul code que vous avez besoin dans votre tête est modèle et le code en ligne. Le destructor n'a pas besoin d'être dans l'en-tête.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top