オーバーロード演算子<<テンプレート化されたクラスの
-
05-07-2019 - |
質問
ストリームを返すバイナリツリーのメソッドを実装しようとしています。メソッドで返されたストリームを使用して、画面にツリーを表示したり、ツリーをファイルに保存したりします。
これらの2つのメソッドは、バイナリツリーのクラスにあります:
宣言:
void streamIND(ostream&,const BinaryTree<T>*);
friend ostream& operator<<(ostream&,const BinaryTree<T>&);
template <class T>
ostream& operator<<(ostream& os,const BinaryTree<T>& tree) {
streamIND(os,tree.root);
return os;
}
template <class T>
void streamIND(ostream& os,Node<T> *nb) {
if (!nb) return;
if (nb->getLeft()) streamIND(nb->getLeft());
os << nb->getValue() << " ";
if (nb->getRight()) streamIND(nb->getRight());
}
このメソッドはUsingTreeクラスにあります:
void UsingTree::saveToFile(char* file = "table") {
ofstream f;
f.open(file,ios::out);
f << tree;
f.close();
}
そのため、演算子&quot;&lt;&lt;&quot;をオーバーロードしました。使用するBinaryTreeクラスの例:cout&lt;&lt;ツリーとofstream f&lt;&lt;ツリー、しかし、次のエラーメッセージが表示されます: `operator&lt;&lt;(std :: basic_ostream&gt;&amp ;, BinaryTree&amp;) 'への未定義の参照
PSツリーには、Wordオブジェクト(intを含む文字列)が格納されます。
私の下手な英語を理解してください。ありがとうございました! そして、私はこのようなエラーですべての時間を無駄にするため、必要なすべてを説明するSTLに関する初心者向けの良いテキストを知りたいです。
編集:saveToFile()のツリーが宣言されています:BinaryTree&lt;単語&gt;ツリー。
解決
問題は、コンパイラが、提供されたテンプレート化された operator&lt;&lt;
ではなく、テンプレート化されていないバージョンを使用しようとしていることです。
クラス内でフレンドを宣言すると、その関数の宣言を囲みスコープに注入します。次のコードには、定数参照によって non_template_test
引数を取る自由関数を宣言する(および定義しない)効果があります。
class non_template_test
{
friend void f( non_template_test const & );
};
// declares here:
// void f( non_template_test const & );
テンプレートクラスでも同じことが起こりますが、この場合は少し直感的ではありません。テンプレートクラス本体内でフレンド関数を宣言する(定義しない)場合、その正確な引数を使用してフリー関数を宣言します。テンプレート関数ではなく、関数を宣言していることに注意してください:
template<typename T>
class template_test
{
friend void f( template_test<T> const & t );
};
// for each instantiating type T (int, double...) declares:
// void f( template_test<int> const & );
// void f( template_test<double> const & );
int main() {
template_test<int> t1;
template_test<double> t2;
}
これらの無料の関数は宣言されていますが、定義されていません。ここで注意が必要なのは、これらの無料関数がテンプレートではなく、宣言されている通常の無料関数であることです。テンプレート関数をミックスに追加すると、次の結果が得られます。
template<typename T> class template_test {
friend void f( template_test<T> const & );
};
// when instantiated with int, implicitly declares:
// void f( template_test<int> const & );
template <typename T>
void f( template_test<T> const & x ) {} // 1
int main() {
template_test<int> t1;
f( t1 );
}
コンパイラーがメイン関数をヒットすると、テンプレート template_test
をタイプ int
でインスタンス化し、それがフリー関数 void f(template_test&lt; int&gt; const&amp ;)
はテンプレート化されていません。呼び出し f(t1)
を見つけると、一致する2つの f
シンボルがあります:非テンプレート f(template_test&lt; int&gt; const&amp;)
template_test
がインスタンス化され、 1
で宣言および定義されたテンプレートバージョンが宣言された(定義されていない)code>。テンプレート化されていないバージョンが優先され、コンパイラはそれを照合します。
リンカーが f
の非テンプレートバージョンを解決しようとすると、シンボルが見つからないため失敗します。
何ができますか? 2つの異なるソリューションがあります。最初のケースでは、インスタンス化する型ごとにコンパイラーに非テンプレート関数を提供させます。 2番目のケースでは、テンプレートバージョンをフレンドとして宣言します。それらは微妙に異なりますが、ほとんどの場合は同等です。
コンパイラーにテンプレート化されていない関数を生成させる:
template <typename T>
class test
{
friend void f( test<T> const & ) {}
};
// implicitly
これには、テンプレート化されていない無料の関数を必要な数だけ作成する効果があります。コンパイラがテンプレート test
内でfriend宣言を見つけると、宣言だけでなく実装も見つけ、両方を囲んでいるスコープに追加します。
テンプレートバージョンをフレンドにする
テンプレートをフレンドにするには、テンプレートを宣言して、必要なフレンドが実際にはテンプレートであり、テンプレート化されていないフリー関数ではないことをコンパイラに通知する必要があります。
template <typename T> class test; // forward declare the template class
template <typename T> void f( test<T> const& ); // forward declare the template
template <typename T>
class test {
friend void f<>( test<T> const& ); // declare f<T>( test<T> const &) a friend
};
template <typename T>
void f( test<T> const & ) {}
この場合、 f
をテンプレートとして宣言する前に、テンプレートを転送宣言する必要があります。 f
テンプレートを宣言するには、まず test
テンプレートを前方宣言する必要があります。フレンド宣言は、フレンドを作成する要素が実際にはテンプレートであり、無料の関数ではないことを識別する山括弧を含むように変更されます。
問題に戻る
特定の例に戻ると、最も簡単な解決策は、フレンド関数の宣言をインライン化することによりコンパイラーに関数を生成させることです:
template <typename T>
class BinaryTree {
friend std::ostream& operator<<( std::ostream& o, BinaryTree const & t ) {
t.dump(o);
return o;
}
void dump( std::ostream& o ) const;
};
そのコードを使用すると、インスタンス化された各型に対してテンプレート化されていない operator&lt;&lt;
を生成するようコンパイラーに強制し、 dump
メソッドの関数デリゲートを生成しますテンプレート。
他のヒント
テンプレート演算子の宣言は必要ありません。演算子&quot; friend&quot;を宣言する必要があります。クラスが他のクラスへのアクセスを許可した場合、この場合はstd :: cout
friend std::ostream& operator << ( std::ostream& os, BinaryTree & tree ) { doStuff( os, tree ); return os; }
&lt;&lt;
演算子をオーバーロードする場合、const参照を使用します:
template <class T>
std::ostream& operator << (std::ostream& os, const BinaryTree<T>& tree)
{
// output member variables here... (you may need to make
// this a friend function if you want to access private
// member variables...
return os;
}
テンプレートとして完全なテンプレート定義(プロトタイプだけでなく)がインクルード(つまり、.h、.hpp)ファイルに含まれていることを確認し、個別のコンパイルが一緒に機能しないようにします。
@Dribeasがどのリンカを使用しているのかわかりませんが、これによりGNUリンカが未定義の参照エラーを確実に発生させる可能性があります。