これは、C++と「継承ベースの古典的なOOP」の論争点の1つです。
考慮しなければならない2つの側面があります:
- typedefは、同じタイプに別の名前を導入します。
std::map<Solver::EnumValue, double>
そしてSmValueProb
、まったく同じものであり、cnaは交換可能に使用されます。
- クラスは、(原則として)他のものとは無関係の新しいタイプを導入します。
クラスの関係は、クラスの「構成」方法と、他のタイプで暗黙の操作と変換を可能にする方法によって定義されます。
特定のプログラミングパラダイム(「inhritance」および「is-a」関係の概念に関連するOOPなど)以外では、継承、暗黙のコンストラクター、暗黙のキャストなど、すべて同じことを行います。つまり、型を使用するようにします。別のタイプのインターフェース全体で、したがって、異なるタイプ間で可能な操作のネットワークを定義します。これは(一般的に言えば)「ポリモーフィズム」です。
表現やランタイム置換可能オブジェクト(従来のOOP)、コンパイル時の置換可能オブジェクト(CRTP)の表現、さまざまなタイプの一般的なアルゴリズム関数(ジェネリックプログラミング)の使用、アルゴリズム構成(関数およびラムダの「キャプチャ」)を表現するための「純粋関数」の使用。
それらのすべては、言語の「機能」をどのように使用する必要があるかについてのいくつかの「ルール」を規定しています。
Luchianが言ったように、std :: mapを継承しても、純粋なOOP置換可能型は生成されません。これは、ベースポインターを削除しても、派生パーツを破棄する方法がわからず、設計上仮想ではないデストラクタであるためです。
しかし、実際には、これは特定のケースにすぎません。仮想ではないため、pbase->find
最終的にオーバーライドされる独自のfind
メソッドも呼び出されません。std::map::find
(しかし、これは未定義ではありません。意図したものではない可能性が高いと非常に明確に定義されています)。
本当の問題は別です。「古典的なOOP置換原則」は、設計において重要かどうか。言い換えると、クラスとそのベースを相互に交換可能に使用し、関数がstd::map*
またはstd::map&
パラメーターを受け取るだけで、それらの関数がstd :: map関数を呼び出すふりをして、メソッドを呼び出すことになりますか?
- はいの場合、継承は進むべき道ではありません。std :: mapには仮想メソッドがないため、ランタイムポリモーフィズムは機能しません。
- いいえの場合、つまり、std :: mapの動作とインターフェイスの両方を再利用して独自のクラスを作成しているだけで、使用法を交換する意図はありません(特に、独自のクラスにnewを割り当てたり、deleteを適用して削除したりすることはありません)。 std :: mapポインタへ)、パラメータとして、
yourclass&
またはyourclass*
パラメータとして関数のセットを提供するだけで、それは完全に問題ありません。関数をstd::mapで使用できなくなり、関数が分離されるため、typedefよりも優れている場合があります。
別の方法は「カプセル化」です。つまり、マップとクラスの明示的なメンバーを作成して、マップをパブリックメンバーとしてアクセスできるようにするか、アクセサー関数を使用してプライベートメンバーにするか、クラスのマップインターフェイスを自分で書き直します。あなたはついに、同じインターフェースとそれ自身の振る舞いを持つ無関係なタイプをガットしました。何百ものメソッドを持つ可能性のあるもののインターフェース全体を書き直すという犠牲を払って。
ノート:
vitual dtorが失われる危険性について考えている人は誰でも、tatを公開してカプセル化しても問題は解決しないことに注意してください。
class myclass: public std::map<something...>
{};
std::map<something...>* p = new myclass;
delete p;
UBは非常に似ています
class myclass
{
public:
std::map<something...> mp;
};
std::map<something...>* p = &((new myclass)->mp);
delete p;
2番目のサンプルには、最初のサンプルと同じ間違いがありますが、あまり一般的ではありません。どちらも、部分オブジェクトへのポインタを使用してオブジェクト全体を操作するふりをしており、部分オブジェクトには何も含まれていないため、「 1つ」です。