9

ネストされた STL コンテナーで構成されるデータ構造があります。

typedef std::map<Solver::EnumValue, double> SmValueProb;
typedef std::map<Solver::VariableReference, Solver::EnumValue> SmGuard;
typedef std::map<SmGuard, SmValueProb> SmTransitions;
typedef std::map<Solver::EnumValue, SmTransitions> SmMachine;

この形式のデータは私のプログラムで簡単にしか使用されず、単にデータを格納する以外に、これらの型に関連付ける意味のある動作はあまりありません。ただし、コンパイラ (VC++2010) は、結果の名前が長すぎると文句を言います。

これ以上詳しく説明することなく、型を STL コンテナーのサブクラスとして再定義すると、うまくいくようです。

typedef std::map<Solver::EnumValue, double> SmValueProb;
class SmGuard : public std::map<Solver::VariableReference, Solver::EnumValue> { };
class SmTransitions : public std::map<SmGuard, SmValueProb> { };
class SmMachine : public std::map<Solver::EnumValue, SmTransitions> { };

STL コンテナーが基本クラスとして使用されることを意図していないことを認識していますが、このシナリオには実際に危険がありますか?

4

3 に答える 3

12

危険が 1 つあります。デストラクタdeleteを持たない基底クラスへのポインタを呼び出すと、virtual未定義の動作が発生します。そうでなければ、あなたは大丈夫です。

少なくともそれが理論です。実際には、MSVC ABI または Itanium ABI (gcc、Clang、icc など)deleteで、仮想デストラクタを持たない基本クラス (-Wdelete-non-virtual-dtorクラスに仮想メソッドがある場合、gcc と clang を使用) でのみ問題が発生します。派生クラスは、非自明なデストラクタ (例: a ) を持つ非静的属性を追加しますstd::string

あなたの特定のケースでは、これは問題ないようです...しかし...

... (コンポジションを使用して)カプセル化し、意味のある(ビジネス指向の) メソッドを公開したい場合があります。危険性が低くなるだけでなく、it->second.find('x')->begin()...

于 2012-11-26T07:49:43.970 に答える
1

これは、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つ」です。

于 2012-11-26T08:18:34.043 に答える
1

はいあります:

std::map<Solver::VariableReference, Solver::EnumValue>* x = new SmGuard;
delete x;

未定義の動作になります。

于 2012-11-26T07:23:42.483 に答える