4

定期的に私を苛立たせるC++の側面は、テンプレートがヘッダーファイル(従来はインターフェイスを記述している)と実装(.cpp)ファイルの間のどこに収まるかを決定することです。多くの場合、テンプレートはヘッダーに含める必要があり、実装を公開し、以前は.cppファイルに含める必要があった余分なヘッダーをプルすることもあります。最近、この問題が再び発生しました。その簡単な例を以下に示します。

#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);
}

実際のクラスを基本クラス、つまり仮想デストラクタとして使用するつもりであることに注意してください。それが重要かどうかは疑わしいですが、念のためカウンターに置いておきました。上記の結果の出力は次のとおりです。

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

Counterこれで、のクラス宣言はすべてヘッダーファイル(例:counter.h)に入れることができます。iostreamを必要とするdtorの実装をcounter.cppに入れることができます。countAndPrint()しかし、iostreamも使用するメンバー関数テンプレートをどうすればよいでしょうか。コンパイルされたcounter.oの外部でインスタンス化する必要があるため、counter.cppでは使用できません。しかし、counter.hに入れるということは、counter.hを含むすべてのものにiostreamも含まれることを意味します。これは、間違っているように見えます(そして、私はこの嫌悪感を乗り越えなければならないかもしれないことを受け入れます)。テンプレートコードを別のファイル(counter.t?)に入れることもできますが、それはコードの他のユーザーにとっては少し意外なことです。Lakosは、私が望むほど実際にはこれに触れていません。C++ FAQベストプラクティスにはなりません。だから私が求めているのは:

  1. コードを私が提案したものに分割するための代替手段はありますか?
  2. 実際には、何が最も効果的ですか?
4

3 に答える 3

5

経験則(理由は明確である必要があります)。

  • プライベートメンバーテンプレートは、.cppファイルで定義する必要があります(クラステンプレートの友達が呼び出す必要がある場合を除く)。
  • 非プライベートメンバーテンプレートは、明示的にインスタンス化されていない限り、ヘッダーで定義する必要があります。

多くの場合、名前を依存させることで多くのヘッダーを含める必要がなくなり、ルックアップやその意味の決定が遅れることがあります。このように、インスタンス化の時点でのみヘッダーの完全なセットが必要です。例として

#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_;
  }
};

これは、CRTPを使用するビジターパターンを実装するために使用したものです。最初はこんな感じでした

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;
      }
      // ...
    }
  }
};

これらの静的キャストのため、これにはすべてのステートメントクラスのヘッダーが必要になります。だから私は型を依存させました、そしてそれから私は前方宣言だけを必要とします

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;
      }
      // ...
    }
  }
};
于 2010-11-06T05:12:14.820 に答える
1

Googleスタイルガイドでは、テンプレートコードを「counter-inl.h」ファイルに入れることを提案しています。インクルードに非常に注意したい場合は、それが最善の方法かもしれません。

ただし、「事故」に​​よってインクルードヘッダーを取得するクライアントはiostream、少なくとも単一のメンバー関数テンプレートしかない場合は、クラスのすべてのコードを単一の論理的な場所に配置するために支払うのはおそらく少額です。

于 2010-11-06T04:50:15.590 に答える
1

実際には、すべてのテンプレートコードをヘッダーに配置するか、テンプレートコードをファイルに配置し.tccて、そのファイルをヘッダーの最後に含めるしかありません。

また、可能であれば、ヘッダーを使用しないように#includeする必要があります<iostream>。これは、コンパイル時に大きな負担がかかるためです。#include結局のところ、ヘッダーは複数の実装ファイルによって作成されることがよくあります。ヘッダーに必要なコードは、テンプレートとインラインコードのみです。デストラクタはヘッダーにある必要はありません。

于 2010-11-06T04:53:48.790 に答える