web-dev-qa-db-ja.com

テンプレート化されたC ++クラスを.hpp / .cppファイルに分割することは可能ですか?

.hpp.cppファイルに分割されているC++テンプレートクラスをコンパイルしようとするとエラーが発生します。

$ g++ -c -o main.o main.cpp  
$ g++ -c -o stack.o stack.cpp   
$ g++ -o main main.o stack.o  
main.o: In function `main':  
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()'  
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'  
collect2: ld returned 1 exit status  
make: *** [program] Error 1  

ここに私のコードがあります:

stack.hpp

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};
#endif

stack.cpp

#include <iostream>
#include "stack.hpp"

template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}

template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}

main.cpp

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

ldはもちろん正しいです。シンボルはstack.oにありません。

この質問 に対する答えは役に立たない、私が既に言っているように。
これ が役立つかもしれませんが、すべてのメソッドを.hppファイルに移動したくありません。

.cppファイル内のすべてを.hppファイルに移動し、スタンドアロンオブジェクトファイルとしてリンクするのではなく、単にすべてを含める唯一の合理的なソリューションですか?それはawfullyseemsいようです!その場合は、以前の状態に戻り、stack.cppの名前をstack.hppに変更し、それで完了です。

86
exscape

別のcppファイルにテンプレートクラスの実装を記述してコンパイルすることはできません。そうするすべての方法は、誰かが主張する場合、個別のcppファイルの使用を模倣するための回避策ですが、実際にはテンプレートクラスライブラリを記述し、実装を隠すためにヘッダーファイルとlibファイルで配布する場合、それは単に不可能です。

理由を知るために、コンパイルプロセスを見てみましょう。ヘッダーファイルはコンパイルされません。それらは前処理されるだけです。前処理されたコードは、実際にコンパイルされたcppファイルでクラブされます。コンパイラがオブジェクトの適切なメモリレイアウトを生成する必要がある場合、テンプレートクラスのデータ型を知る必要があります。

実際、テンプレートクラスはクラスではなく、引数からデータ型の情報を取得した後、コンパイル時にコンパイラによって宣言と定義が生成されるクラスのテンプレートであることを理解する必要があります。メモリレイアウトを作成できない限り、メソッド定義の命令は生成できません。クラスメソッドの最初の引数は「this」演算子であることに注意してください。すべてのクラスメソッドは、名前のマングリングと、操作対象のオブジェクトとしての最初のパラメーターを持つ個々のメソッドに変換されます。 'this'引数は、ユーザーが有効な型引数でオブジェクトをインスタンス化しない限り、テンプレートクラスのケースがコンパイラで使用できないオブジェクトのサイズを実際に示します。この場合、メソッド定義を別のcppファイルに入れてコンパイルしようとすると、オブジェクトファイル自体はクラス情報とともに生成されません。コンパイルは失敗しません。オブジェクトファイルを生成しますが、オブジェクトファイル内のテンプレートクラスのコードは生成しません。これが、リンカがオブジェクトファイル内のシンボルを見つけることができず、ビルドが失敗する理由です。

ここで、重要な実装の詳細を隠す代替手段は何ですか?インターフェースを実装から分離する背後にある主な目的は、実装の詳細をバイナリ形式で隠すことです。ここで、データ構造とアルゴリズムを分離する必要があります。テンプレートクラスは、アルゴリズムではなくデータ構造のみを表す必要があります。これにより、テンプレートクラスで動作する内部のクラス、またはデータを保持するためにそれらを使用する、テンプレート化されていない個別のクラスライブラリに、より価値のある実装の詳細を隠すことができます。テンプレートクラスには、実際には、データを割り当て、取得、設定するためのコードが少なくなります。残りの作業は、アルゴリズムクラスによって行われます。

この議論が役立つことを願っています。

140
Sharjith N.

isは、どのインスタンス化が必要かを知っている限り可能です。

Stack.cppの最後に次のコードを追加すると、動作します:

template class stack<int>;

スタックの非テンプレートメソッドはすべてインスタンス化され、リンク手順は正常に機能します。

82
Benoît

この方法でできます

// xyz.h
#ifndef _XYZ_
#define _XYZ_

template <typename XYZTYPE>
class XYZ {
  //Class members declaration
};

#include "xyz.cpp"
#endif

//xyz.cpp
#ifdef _XYZ_
//Class definition goes here

#endif

これは Daniweb で議論されています

[〜#〜] faq [〜#〜] でもC++エクスポートキーワードを使用しています。

8
Sadanand

いいえ、できません。 exportキーワードがないわけではありません。これは、すべての意図と目的に対して実際には存在しません。

できる最善の方法は、関数の実装を「.tcc」または「.tpp」ファイルに配置し、.hppファイルの最後に.tccファイルを#includeすることです。ただし、これは単なる外観です。ヘッダーファイルにすべてを実装するのと同じです。これは、単にテンプレートを使用するために支払う価格です。

6
Charles Salvia

テンプレートコードをヘッダーとcppに分離しようとする主な理由は2つあると思います。

1つは単なる優雅さです。私たちは皆、読みやすく、管理しにくく、後で再利用できるコードを書くのが好きです。

その他は、コンパイル時間の短縮です。

私は現在(いつものように)OpenCLと組み合わせてシミュレーションソフトウェアをコーディングしていますが、HW機能に応じて必要に応じてfloat(cl_float)またはdouble(cl_double)タイプを使用して実行できるようにコードを保持したいと考えています。現在、これはコードの先頭で#define REALを使用して行われていますが、これはあまりエレガントではありません。必要な精度を変更するには、アプリケーションを再コンパイルする必要があります。実際の実行時タイプはないので、当面はこれで生きなければなりません。幸いなことに、OpenCLカーネルはランタイムとしてコンパイルされており、単純なsizeof(REAL)によりカーネルコードランタイムを適宜変更できます。

はるかに大きな問題は、アプリケーションがモジュール式であっても、補助クラス(シミュレーション定数を事前に計算するクラスなど)を開発する際にもテンプレート化する必要があることです。最終的なテンプレートクラスシミュレーションにはこれらのファクトリクラスのインスタンスがあるため、これらのクラスはすべてクラス依存関係ツリーの最上部に少なくとも1回表示されます。つまり、事実上、ファクトリクラスに小さな変更を加えるたびに、ソフトウェアを再構築する必要があります。これは非常に迷惑ですが、より良い解決策を見つけることができないようです。

3
Meteorhead

共通機能fooすべてのテンプレートパラメータを非テンプレートクラスに抽出できる場合(場合によってはタイプセーフではない)、実装のほとんどをcppファイルに隠すことができる場合があります。ヘッダーには、そのクラスへのリダイレクト呼び出しが含まれます。 「テンプレートの膨張」問題と戦うとき、同様のアプローチが使用されます。

2

問題は、テンプレートが実際のクラスを生成せず、単にtemplateがクラスを生成する方法をコンパイラに伝えることです。具体的なクラスを生成する必要があります。

簡単で自然な方法は、ヘッダーファイルにメソッドを配置することです。しかし、別の方法があります。

.cppファイルで、必要なすべてのテンプレートのインスタンス化とメソッドへの参照がある場合、コンパイラはそれらを生成し、プロジェクト全体で使用します。

新しいstack.cpp:

#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
static void DummyFunc() {
    static stack<int> stack_int;  // generates the constructor and destructor code
    // ... any other method invocations need to go here to produce the method code
}
2
Mark Ransom

スタックがどのタイプで使用されるかがわかっている場合、それらをcppファイルで明示的にインスタンス化し、関連するすべてのコードをそこに保持できます。

これらを複数のDLLにエクスポートすることもできます(!).

Double/floatをテンプレートにしたmath/geomライブラリで使用しましたが、かなり多くのコードがありました。 (私はその時にグーグルで探しましたが、今日はそのコードを持っていません。)

2
Macke

これはかなり古い質問ですが、 cppcon 2016のArthur O'Dwyer のプレゼンテーションを見るのは面白いと思います。良い説明、多くの主題がカバーされ、必見です。

1
FreeYourSoul

Hppファイルにすべてを含める必要があります。問題は、他のcppファイルに必要であるとコンパイラが認識するまでクラスが実際に作成されないことです。そのため、その時点でテンプレートクラスをコンパイルするためのすべてのコードが必要です。

私がしがちなことの1つは、テンプレートを汎用の非テンプレートパーツ(cpp/hppに分割可能)と、非テンプレートクラスを継承する型固有のテンプレートパーツに分割しようとすることです。

1
Aaron

#include "stack.cpp の終わりに stack.hpp。実装が比較的大きい場合、および通常のコードと区別するために、.cppファイルの名前を別の拡張子に変更する場合にのみ、このアプローチをお勧めします。

1
lyricat

テンプレートは必要に応じてコンパイルされるため、これによりマルチファイルプロジェクトの制限が強制されます。テンプレートクラスまたは関数の実装(定義)は、その宣言と同じファイルに存在する必要があります。つまり、インターフェイスを個別のヘッダーファイルに分離することはできず、テンプレートを使用するファイルにはインターフェイスと実装の両方を含める必要があります。

0
ChadNC

1).hファイルと.cppファイルを分離する主な理由を覚えておいてください。クラスの実装を、クラスの.hを含むユーザーのコードにリンクできる個別にコンパイルされたObjコードとして隠すためです。

2)非テンプレートクラスには、すべての変数が具体的かつ具体的に.hおよび.cppファイルで定義されています。したがって、コンパイラは、コンパイル/翻訳する前にクラスで使用されるすべてのデータ型に関する必要な情報を取得しますオブジェクト/マシンコードを生成しますタイプ:

        TClass<int> myObj;

3)このインスタンス化の後でのみ、コンパイラは渡されたデータ型と一致するテンプレートクラスの特定のバージョンを生成します。

4)したがって、.cppは、ユーザー固有のデータ型を知らずに個別にコンパイルすることはできません。そのため、ユーザーが必要なデータ型を指定するまで「.h」内のソースコードのままにしておく必要があり、特定のデータ型に生成してコンパイルできます。

0
Aaron01

別の可能性は次のようなことです:

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};

#include "stack.cpp"  // Note the include.  The inclusion
                      // of stack.h in stack.cpp must be 
                      // removed to avoid a circular include.

#endif

この提案はスタイルの問題として嫌いですが、あなたに合うかもしれません。

0
luke

「export」キーワードは、テンプレート実装をテンプレート宣言から分離する方法です。これは、既存の実装なしでC++標準に導入されました。やがて、実際に実装したのは数人のコンパイラだけです。 で詳細情報を読むエクスポートに関するIT記事を通知する

0
Shailesh Kumar