テンプレート化されたC ++クラスを作成する場合、通常は3つのオプションがあります。
(1)宣言と定義をヘッダーに入れます。
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f()
{
...
}
};
または
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
template <typename T>
inline void Foo::f()
{
...
}
プロ:
短所:
- インターフェイスとメソッドの実装が混在しています。これは「単なる」読みやすさの問題です。これは通常の.h / .cppアプローチとは異なるため、これを保守できないと考える人もいます。ただし、これは、C#やJavaなどの他の言語では問題ないことに注意してください。
- 再構築の影響が大きい:
Foo
メンバーとして新しいクラスを宣言する場合は、を含める必要がありますfoo.h
。つまり、実装を変更するFoo::f
と、ヘッダーファイルとソースファイルの両方に反映されます。
再構築の影響を詳しく見てみましょう。テンプレート化されていないC ++クラスの場合は、宣言を.hに、メソッド定義を.cppに配置します。このように、メソッドの実装が変更された場合、1つだけの.cppを再コンパイルする必要があります。.hにすべてのコードが含まれている場合、これはテンプレートクラスでは異なります。次の例を見てください。
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
ここでの唯一の使用法Foo::f
はinside bar.cpp
です。ただし、の実装を変更した場合Foo::f
、との両方bar.cpp
をqux.cpp
再コンパイルする必要があります。のFoo::f
一部がQux
直接を使用していない場合でも、両方のファイルにliveが実装されていますFoo::f
。大規模なプロジェクトの場合、これはすぐに問題になる可能性があります。
(2)宣言を.hに、定義を.tppに入れ、それを.hに含めます。
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
#include "foo.tpp"
// foo.tpp
#pragma once // not necessary if foo.h is the only one that includes this file
template <typename T>
inline void Foo::f()
{
...
}
プロ:
- 非常に便利な使用法(ヘッダーを含めるだけ)。
- インターフェイスとメソッドの定義は分離されています。
短所:
このソリューションは、.h / .cppのように、宣言とメソッド定義を2つの別々のファイルに分けます。ただし、ヘッダーに直接メソッド定義が含まれているため、このアプローチには(1)と同じ再構築の問題があります。
(3).hに宣言と.tppに定義を入れますが、.hには.tppを含めません。
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
...
}
プロ:
- .h / .cppの分離と同様に、再構築の影響を軽減します。
- インターフェイスとメソッドの定義は分離されています。
短所:
- 不便な使い方:
Foo
クラスBar
にメンバーを追加するときfoo.h
は、ヘッダーに含める必要があります。Foo::f
.cpp を呼び出す場合は、そこにも含めるfoo.tpp
必要があります。
実際に使用Foo::f
する.cppファイルのみを再コンパイルする必要があるため、このアプローチは再構築の影響を減らします。ただし、これには代償が伴いますfoo.tpp
。これらすべてのファイルにを含める必要があります。上記の例を取り上げ、新しいアプローチを使用します。
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
#include "foo.tpp"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
あなたが見ることができるように、唯一の違いは、追加での含まれているfoo.tpp
の中でbar.cpp
。これは不便であり、メソッドを呼び出すかどうかに応じて、クラスに2番目のインクルードを追加することは非常に醜いようです。ただし、再構築の影響は少なくbar.cpp
なりますFoo::f
。の実装を変更した場合にのみ、再コンパイルする必要があります。ファイルをqux.cpp
再コンパイルする必要はありません。
概要:
ライブラリを実装する場合、通常、再構築の影響を気にする必要はありません。ライブラリのユーザーはリリースを取得して使用します。ライブラリの実装は、ユーザーの日常の作業で変更されません。そのような場合、ライブラリは(1)または(2)のアプローチを使用でき、どちらを選択するかは好みの問題です。
ただし、アプリケーションで作業している場合、または会社の内部ライブラリで作業している場合、コードは頻繁に変更されます。したがって、再構築の影響に注意する必要があります。開発者に追加のインクルードを受け入れさせる場合は、アプローチ(3)を選択することをお勧めします。