1. ホーム
  2. c++

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

2022-10-03 08:53:41

質問

C++のテンプレートクラスをコンパイルしようとすると、エラーが発生します。 .hpp.cpp というファイルを作成します。

$ 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

スタック.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;
}

メイン.cpp :

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

ld はもちろん正しいです。シンボルは stack.o .

の答えは この質問 は、すでに書いてあるとおりにしているので、役に立ちません。

これは は役に立つかもしれませんが、私はいちいちメソッドを .hpp ファイルに移動したくありません。

のすべてを移動させることが唯一の合理的な解決策でしょうか? .cpp ファイルから .hpp ファイルへリンクし、スタンドアロン オブジェクト ファイルとしてリンクするのではなく、単にすべてをインクルードするのですか?それは 非常に 醜い!それならいっそのこと、以前の状態に戻して、リネームして stack.cppstack.hpp に変更して終了です。

どのように解決するのですか?

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

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

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

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

この議論が参考になれば幸いです。