web-dev-qa-db-ja.com

C ++:std :: mapから継承

std::mapから継承したいのですが、私が知る限り、std::mapには仮想デストラクタがありません。

したがって、適切なオブジェクトの破棄を確実にするために、デストラクタでstd::mapのデストラクタを明示的に呼び出すことは可能ですか?

23

仮想ではない場合でも、デストラクタは呼び出されますが、それは問題ではありません。

std::mapへのポインタを介してタイプのオブジェクトを削除しようとすると、未定義の動作が発生します。

継承の代わりにコンポジションを使用します。stdコンテナは継承されることを意図しておらず、継承すべきではありません。

std::mapの機能を拡張したいと思います(最小値を見つけたいとしましょう)。その場合、2つのはるかに優れたオプションがありますlegal、オプション:

1)提案されているように、代わりに合成を使用できます。

template<class K, class V>
class MyMap
{
    std::map<K,V> m;
    //wrapper methods
    V getMin();
};

2)無料機能:

namespace MapFunctionality
{
    template<class K, class V>
    V getMin(const std::map<K,V> m);
}
34
Luchian Grigore

誤解があります。継承(純粋なOOPの概念の外では、C++はそうではない)は、「名前のないメンバーと、減衰機能を備えた構成」にすぎません。

仮想関数がない(そしてこの意味でデストラクタは特別ではない)ため、オブジェクトはポリモーフィックではありませんが、「動作を再利用してネイティブインターフェイスを公開する」だけの場合、継承は要求どおりに実行されます。

デストラクタの呼び出しは常に仕様によって連鎖されるため、デストラクタを相互に明示的に呼び出す必要はありません。

_#include <iostream>
unsing namespace std;

class A
{
public:
   A() { cout << "A::A()" << endl; }
   ~A() { cout << "A::~A()" << endl; }
   void hello() { cout << "A::hello()" << endl; }
};

class B: public A
{
public:
   B() { cout << "B::B()" << endl; }
   ~B() { cout << "B::~B()" << endl; }
   void hello() { cout << "B::hello()" << endl; }
};

int main()
{
   B b;
   b.hello();
   return 0;
}
_

出力します

_A::A()
B::B()
B::hello()
B::~B()
A::~A()
_

AをBに埋め込む

_class B
{
public:
   A a;
   B() { cout << "B::B()" << endl; }
   ~B() { cout << "B::~B()" << endl; }
   void hello() { cout << "B::hello()" << endl; }
};
_

それはまったく同じように出力されます。

「デストラクタが仮想でない場合は派生しない」はC++の必須の結果ではありませんが、Cの前に発生する一般的に受け入れられている書かれていない(仕様には何もありません:ベースでdeleteを呼び出すUBを除いて)ルールです++ 99、動的継承と仮想関数によるOOPがサポートされている唯一のプログラミングパラダイムであった場合。

もちろん、世界中の多くのプログラマーは、その種の学校(iostreamをプリミティブとして教えてから、配列とポインターに移動するのと同じ)で骨を作成しました。そして最後のレッスンで、先生は「ああ... tehreはベクトル、文字列、その他の高度な機能を備えたSTLでもある」と言っています)そして今日、C++がマルチパラダイムになったとしても、この純粋なOOPを主張します。 _ルール。

私のサンプルでは、​​A :: 〜A()はA :: helloとまったく同じように仮想化されていません。どういう意味ですか?

単純:同じ理由で、_A::hello_を呼び出しても_B::hello_は呼び出されず、A::~A()を(削除によって)呼び出してもB::~B()は生成されません。 プログラミングスタイルで-最初のアサーションを受け入れることができる場合、2番目のを受け入れることができない理由はありません。私のサンプルでは、​​A :: 〜Aは仮想ではなく、それが何を意味するのか知っているので、_A* p = new B_を受け取る_delete p_はありません

Bの2番目の例を使用すると、まったく同じ理由が発生しません。A* p = &((new B)->a);と_delete p;_はありますが、この2番目のケースは、最初のケースと完全に二重であり、明らかな理由もなく、誰も面白くないように見えます。 。

唯一の問題は「メンテナンス」です。つまり、-yopurコードがOOPプログラマーによって表示された場合、それ自体が間違っているからではなく、そうするように言われたために、それを拒否します。 。

実際、「デストラクタが仮想でない場合は導出しない」というのは、ほとんどのプログラマーが、削除を呼び出すことができないことを知らないプログラマーが多すぎると信じているためです。ベースへのポインタ。 (これが礼儀正しくない場合は申し訳ありませんが、30年以上のプログラミング経験の後、他の理由はわかりません!)

しかし、あなたの質問は異なります:

B :: 〜B()を(削除またはスコープ終了によって)呼び出すと、A(埋め込みまたは継承)はいずれの場合も一部であるため、常にA :: 〜A()になります- Bの。


ルチアンのコメントに続いて:彼のコメントで上に言及された未定義の振る舞いは、仮想デストラクタのないオブジェクトのベースへのポインタの削除に関連しています。

OOP学校によると、これは「仮想デストラクタが存在しない場合は派生しない」というルールになります。

ここで私が指摘しているのは、その学校の理由は、すべてのOOP指向オブジェクトがポリモーフィックである必要があり、すべてがポリモーフィックであるという事実に依存しているということです。オブジェクト置換。それらの主張をすることによって、その学校は、純粋なOOPプログラムがそのUBを経験しないように、派生したものと置き換え不可能なものの間の交差を無効にすることを意図的に試みています。

私の立場は、単純に、C++が単なるOOPではなく、すべてのC++オブジェクトがデフォルトでOOP指向である必要があるわけではなく、OOPを認めることが必ずしも必要ではないことを認めています。また、C++の継承が必ずしもOOP置換に役立つとは限らないことも認めています。

std :: mapはポリモーフィックではないため、置き換えることはできません。 MyMapは同じです。ポリモーフィックではなく、置き換え可能でもありません。

Std :: mapを再利用し、同じstd :: mapインターフェースを公開するだけです。そして、継承は、再利用された関数を呼び出すだけの、書き直された関数の長い定型文を回避する方法にすぎません。

Std :: mapには仮想dtorがないため、MyMapには仮想dtorがありません。そして、これは-私にとって-これらはポリモーフィックオブジェクトではなく、一方を他方の代わりに使用してはならないことをC++プログラマーに伝えるのに十分です。

私は、この立場が今日ほとんどのC++専門家によって共有されていないことを認めなければなりません。しかし、(私の唯一の個人的な意見ですが)これは、C++の必要性のためではなく、奉仕する教義としてのOOPに関連する彼らの歴史のためだけだと思います。私にとって、C++は純粋なOOP言語ではなく、OOPが従わない、または必要とされない状況では、必ずしもOOPパラダイムに従う必要はありません。

19

std::map [...]から継承したい

どうして ?

継承する2つの伝統的な理由があります:

  • そのインターフェース(したがって、それに対してコーディングされたメソッド)を再利用する
  • その動作を再利用する

前者はここでは意味がありません。mapにはvirtualメソッドがないため、継承によってその動作を変更することはできません。後者は継承の使用の倒錯であり、最終的にはメンテナンスを複雑にするだけです。


使用目的が明確にわからない場合(質問のコンテキストが不足している場合)、本当に必要なのは、いくつかのボーナス操作を備えたマップのようなコンテナーを提供することだと思います。これを実現するには、次の2つの方法があります。

  • 構成:新しいオブジェクトを作成します。これは含む a std::mapであり、適切なインターフェイスを提供します
  • 拡張機能:std::mapで動作する新しいフリー関数を作成します

後者はより単純ですが、よりオープンでもあります。std::mapの元のインターフェースはまだ広く開かれています。したがって、制限操作には適していません。

前者は間違いなくより重いですが、より多くの可能性を提供します。

2つのアプローチのどちらがより適切であるかを決定するのはあなた次第です。

13
Matthieu M.

@MatthieuMあなたが言った

Std :: mapから継承したい[...]

どうして ?

継承する2つの伝統的な理由があります:

  1. to そのインターフェースを再利用する(したがって、それに対してコーディングされたメソッド)
  2. to その動作を再利用する

マップには仮想メソッドがないため、継承によってその動作を変更することはできないため、前者はここでは意味がありません。後者は継承の使用の倒錯であり、最終的にはメンテナンスを複雑にするだけです。

「前者」について:

clear()関数は仮想であり、値のインスタンスを指すすべてを削除するイテレーターを使用して、派生クラスでstd::map<key,valueClass*>::clear()をオーバーライドすることは非常に理にかなっています。偶発的なメモリリークを防ぐために、基本クラスclear()を呼び出す前にクラスを作成します。これは、私が実際に使用したトリックです。誰かがクラスへのポインタへのマップを使用したいと思う理由については、多態性と参照が再割り当てできないということは、STLコンテナで使用できないことを意味します。代わりに、reference_wrapperまたは_shared_ptr_(C++ 11機能)などのスマートポインターの使用を提案することもできますが、ライブラリを作成するときに、C++ 98コンパイラに制限された誰かができるようにしたい場合使用するには、ブーストの要件を設定しない限り、これらはオプションではありません。これも望ましくない場合があります。また、実際にマップにそのコンテンツの唯一の所有権を持たせたい場合は、reference_wrapperやスマートポインターのほとんどの実装を使用したくありません。

「後者」に関して:

メモリを指すポインタを自動削除するポインタへのマップが必要な場合は、他の「すべての」マップ動作を再利用し、clearをオーバーライドすることは私にとって非常に理にかなっています。もちろん、割り当て/コピーコンストラクタをオーバーライドしてクローンを作成することもできます。 valueClassのポイントされたインスタンスを二重に削除しないように、マップをコピーするときにオブジェクトをポイントしました。

しかし、それを実装するのに必要なコーディングはごくわずかです。

また、派生クラスマップの宣言の最初の2行として保護された_typedef std::map<key,valueClass*> baseClassMap;_を使用します。これにより、後でオーバーライドされたbaseClassMap::clear();関数でclear()を呼び出すことができます。イテレータループは、派生マップに含まれる_valueClass*_のすべてのインスタンスを削除します。これにより、_valueClass*_のタイプが変更された場合のメンテナンスが容易になります。

要点はですが、良いコーディング慣行では適用範囲が限られているかもしれませんが、決して良いとは言えないと思います。地図から降りるアイデア。しかし、大量の追加ソースコードを追加せずに同じ自動メモリ管理効果を実現する方法について私が考えていなかったというより良い考えがあるかもしれません(たとえば、_std::map_を集約する)。

2
Keith