3

C++ では、次のクラス階層を想定しています。

class BaseClass { };
class ChildClass : public BaseClass { };

さらに、共通のテンプレート化された基本クラスを持つこれら 2 つのクラスのファクトリ クラスを想定します。

template<typename T>
class Factory {
public:
  virtual T* create() = 0;
};

class BaseClassFactory : public Factory<BaseClass> {
public:
  virtual BaseClass* create() {
    return new BaseClass(&m_field);
  }
private:
  SomeClass m_field;
};

class ChildClassFactory : public Factory<ChildClass> {
public:
  virtual ChildClass* create() {
    return new ChildClass(&m_field);
  }
private:
  SomeOtherClass m_field; // Different class than SomeClass
};

と のサイズ/内部構造はChildClassFactoryBaseClassFactoryフィールドが異なるため異なることに注意してください。

ChildClassFactoryに(または)のインスタンスがある場合、Factory<ChildClass>安全にFactory<BaseClass>(経由でreinterpret_cast) にキャストできますか?

Factory<ChildClass>* childFactory = new ChildClassFactory();

// static_cast doesn't work - need to use reinterpret_cast
Factory<BaseClass>* baseFactory = reinterpret_cast<Factory<BaseClass>*>(childFactory);

// Does this work correctly? (i.e. is "cls" of type "ChildClass"?)
BaseClass* cls = baseFactory->create();

テンプレート化されたクラスを常にこのようにキャストできるとは限らないことは知っていますが、この特別なケースでは、キャストは安全であるべきですよね?

Visual C++ 2010 でテストしましたが、動作します。私の質問は、これが他のコンパイラに移植可能かどうかです。

更新:いくつかの混乱があったので、私の例で重要な (と思われる) ものをもう少し明確にさせてください:

  • ChildClassの子クラスですBaseClass
  • のユーザーは、Factory<BaseClass>のどの子クラスが作成されるかを知りませんBaseClass。彼が知っているのは、それBaseClassが作成されたということだけです。
  • Factory<T>独自のフィールドはありません (vtable 以外)。
  • Factory::create()virtual
4

3 に答える 3

5

いいえそうではありません。reinterpret_castいくつかの特別な場合を除いて、 other than の結果をキャスト バックに使用することはできません。

ISO14882:2011(e) 5.2.10-7:

オブジェクト ポインターは、異なる型のオブジェクト ポインターに明示的に変換できます。 T1 と T2 の両方が標準レイアウト型 (3.9) であり、T2 のアラインメント要件が T1 のアラインメント要件よりも厳密でない場合、またはいずれかの型が void である場合。「T1 へのポインター」型の prvalue を「T2 へのポインター」型 (T1 と T2 はオブジェクト型であり、T2 のアラインメント要件は T1 のアラインメント要件より厳密ではない) に変換し、元の型に戻すと、元の型が生成されます。ポインター値。他のそのようなポインター変換の結果は規定されていません。

起こり得る失敗のシナリオをより明確にするために、多重継承を検討してください。この場合、 static_castordynamic_castを使用するとポインター値が調整されることがありますが、そうでreinterpret_castはありません。A*この例で からにキャストすることを検討してB*ください。

struct A { int x; };
struct B { int y; };
struct C : A, B { };

コードが別の方法で失敗する方法を理解するには、ほとんどのコンパイラが仮想関数呼び出しメカニズムを実装する方法を検討してください: 仮想ポインターを使用します。のインスタンスにChildClassFactoryは、 の仮想テーブルを指す仮想ポインタがありますChildClassFactory。コンパイラreinterpret_castは、同じ/類似のレイアウトを持つ仮想テーブルを指す仮想ポインタを期待しているため、この獣がたまたま「機能」するだけです。ただし、仮想関数を指す値がまだ含まれているため、ChildCLassFactoryこれらの関数が呼び出されます。これはすべて、未定義の動作を呼び出してからずっと後のことです。車で大きな峡谷に飛び込み、まだ地面に着いていないという理由だけで、「すべてが順調に進んでいる」と考えているようなものです。

于 2012-01-26T13:40:37.400 に答える
0

いいえ、reinterpret_cast は、正しいアドレス操作を実行しないため、低レベル コードにのみ使用されます。代わりに static_cast または dynamic_cast を使用してください。

GoF ファクトリ パターンに収まらない 2 つのファクトリが必要なのはなぜですか。

reinterpret_cast は遅く (実行時チェック)、優れた OO 設計ではない (言語にポリモフィズム ビルドを使用する必要がある) ため、これを行う方法ではありません。

代わりに、目的の型を生成するファクトリ クラスでコンストラクターを作成し、これらに個々の型のコンストラクターを呼び出させます。

ファクトリ パターンを使用すると、実装の変更を無視することができます。これは、依存関係を最小限に抑えることができるため、良いことであり、将来のコードの保守が容易になります。

于 2012-01-26T13:41:09.663 に答える
0

上記の元の回答にチェックを入れましたが(彼にクレジットを与えるため)、ここで学んだことを要約すると思いました。

したがって、基本的な問題は、仮想呼び出しのディスパッチをどのように実装する必要があるかが定義されていないことです。

これは、仮想呼び出しのディスパッチに内部的に使用されるデータ構造 (vtable など) が、同じテンプレートから作成されたテンプレートのインスタンス間でビット互換性がある場合とない場合があることを意味します。

于 2012-01-26T15:06:51.853 に答える