質問
仮想関数とC ++継承メカニズムの使用と、テンプレートやBoostコンセプトのようなものの使用との関係はどうですか?
可能なことはかなり重複しているようです。つまり、どちらのアプローチでも多態的な振る舞いを達成することが可能と思われます。それで、いつ一方を他方よりも優先するのが理にかなっていますか?
これを取り上げる理由は、コンテナ自体が階層関係にあるテンプレートコンテナがあるためです。特定のコンテナを気にせずにこれらのコンテナを使用するアルゴリズムを書きたいと思います。また、一部のアルゴリズムは、テンプレートタイプが特定の概念(たとえば、比較可能)を満たしていることを知ることでメリットが得られます。
だから、一方では、コンテナが多態的に振る舞うことを望みます。一方、いくつかのアルゴリズムを正しく実装する場合は、概念を使用する必要があります。ジュニア開発者は何をすべきですか?
解決
概念は一種のメタインターフェースと考えています。彼らは能力の後にタイプを分類します。次のC ++バージョンは、ネイティブの概念を提供します。 C ++ 1xの概念に出会うまで理解できませんでした。C++ 1xの概念と、異なるが関係のない型を組み合わせることを可能にする方法を見つけました。 Range
インターフェースがあると想像してください。 2つの方法でモデル化できます。 1つは、サブタイプ関係:
class Range {
virtual Iterator * begin() = 0;
virtual Iterator * end() = 0;
virtual size_t size() = 0;
};
もちろん、派生するすべてのクラスはRangeインターフェースを実装し、関数で使用できます。しかし、今では制限されています。配列はどうですか?それも範囲です!
T t[N];
begin() => t
end() => t + size()
size() => N
残念ながら、そのインターフェースを実装するRangeクラスから配列を派生させることはできません。追加のメソッドが必要です(オーバーロード)。そして、サードパーティのコンテナはどうですか?ライブラリのユーザーは、コンテナを関数と一緒に使用したい場合があります。しかし、彼はコンテナの定義を変更することはできません。ここで、概念が登場します:
auto concept Range<typename T> {
typename iterator;
iterator T::begin();
iterator T::end();
size_t T::size();
}
ここで、 T
に適切なメンバー関数がある場合に満たすことができる、あるタイプのサポートされている操作について何か言います。ライブラリに、汎用関数を記述します。これにより、必要な操作をサポートしている限り、任意のタイプを受け入れることができます:
template<Range R>
void assign(R const& r) {
... iterate from r.begin() to r.end().
}
これは素晴らしい代替性です。 Any タイプは、一部のインターフェースを積極的に実装するタイプだけでなく、コンセプトに準拠した法案に適合します。次のC ++標準はさらに進んでいます:プレーンな配列に適合する Container
の概念(ある種の概念に適合する型を定義する概念マップによる)とその他の概念を定義します、既存の標準コンテナ。
これを取り上げる理由は、コンテナ自体が階層関係にあるテンプレートコンテナがあるためです。特定のコンテナを気にせずにこれらのコンテナを使用するアルゴリズムを書きたいと思います。また、一部のアルゴリズムは、テンプレートタイプが特定の概念(たとえば、比較可能)を満たしていることを知ることでメリットが得られます。
実際には、テンプレートを使用して両方を実行できます。階層関係を維持してコードを共有し、一般的な方法でアルゴリズムを記述できます。たとえば、コンテナが同等であることを伝えるために。これは、標準のランダムアクセス/転送/出力/入力イテレータカテゴリが実装されているようなものです。
// tag types for the comparator cagetory
struct not_comparable { };
struct basic_comparable : not_comparable { };
template<typename T>
class MyVector : public BasicContainer<T> {
typedef basic_comparable comparator_kind;
};
/* Container concept */
T::comparator_kind: comparator category
実際には、これを行う合理的な簡単な方法です。これで、関数を呼び出すことができ、正しい実装に転送されます。
template<typename Container>
void takesAdvantage(Container const& c) {
takesAdvantageOfCompare(c, typename Container::comparator_kind());
}
// implementation for basic_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, basic_comparable) {
...
}
// implementation for not_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, not_comparable) {
...
}
実際には、それを実装するために使用できるさまざまな手法があります。別の方法は、 boost :: enable_if
を使用して、毎回異なる実装を有効または無効にすることです。
他のヒント
はい、両方のメカニズムで多態的な動作が可能です。実際、両方とも多型とも呼ばれています。
仮想関数は動的ポリモーフィズムを提供し(実行時に決定されるため)、テンプレートは静的ポリモーフィズムを提供します(すべてはコンパイル時に決定されます)。
そして、それはどちらを好むかという質問にも答えるべきです。可能な限り、作業をコンパイル時に移行することをお勧めします。したがって、それをうまく処理できる場合は、テンプレートを使用してポリモーフィズムのニーズを解決してください。それが不可能な場合(コンパイル時に正確な型がわからないため、実行時の型情報を使用する必要があるため)、動的なポリモーフィズムにフォールバックします。
(もちろん、どちらか一方を好む他の理由があるかもしれません。特に、テンプレートでは、問題になるかもしれないヘッダーファイルに多くのコードを移動する必要があり、コンパイル速度が低下する傾向があります。また、問題になる場合もあれば、そうでない場合もあります。)
コンパイル時に決定できる場合は、テンプレートを使用します。それ以外の場合は、継承と仮想関数を使用します。
この特定のケースでは、次のようなことができます
template<typename T>
class ContainerBase{};
template<typename T>
class ContainerDerived : public ContainerBase<T> {};
各「Container」型は各テンプレート型に対して一意であるため、各コンテナ型のメンバー関数がテンプレート型の特性に特化できない理由はありません。
コンパイル時と実行時のポリモーフィズムの違いの簡単な例として、次のコードを検討してください。
template<typename tType>
struct compileTimePolymorphism
{ };
// compile time polymorphism,
// you can describe a behavior on some object type
// through the template, but you cannot interchange
// the templates
compileTimePolymorphism<int> l_intTemplate;
compileTimePolymorphism<float> l_floatTemplate;
compileTimePolymorphism *l_templatePointer; // ???? impossible
struct A {};
struct B : public A{};
struct C : public A{};
// runtime polymorphism
// you can interchange objects of different type
// by treating them like the parent
B l_B;
C l_C:
A *l_A = &l_B;
l_A = &l_C;
コンパイル時のポリモーフィズムは、あるオブジェクトの動作が他のオブジェクトに依存する場合に適したソリューションです。オブジェクトの動作を変更する必要がある場合、実行時多態性が必要です。
この2つは、ポリモーフィックなテンプレートを定義することで組み合わせることができます:
template<typename tType>
struct myContainer : public tType
{};
問題は、コンテナの動作を変更する必要がある場所(ランタイムポリモーフィズム)、および動作が含まれるオブジェクトに依存する場所(コンパイル時ポリモーフィズム)です。