0

変更不可能なヘッダーで宣言されたフォワードC構造体があります。それにコンビニエンスメンバー関数を「仮想的に」追加したいと思います。明らかに、私の最初の選択は、構造体を拡張し、派生クラスにメソッドを追加することです。構造体自体がヘッダーで「転送」として宣言されているため、実行できません。そのため、「エラー:不完全な型の無効な使用...」というエラーが発生します。古い構造体の単一の要素を使用して新しい構造体を定義しようとすると、同様のエラーが発生します。これは最悪だ。

ただし、reinterpret_castを使用してハッカーを実行し、とにかく機能させることができると考えていました。それが行く方法はこれです:

//defined in header
struct A forward;
void do_something_with_A(A* a, int arg);

//defined in my wrapper
struct B {
  B* wrap(A* a) {return reinterpret_cast<B*>(a); }
  void do_something(int arg) {do_something_with_A(reinterpret_cast<A*>(this),arg); }
}

タイプBからタイプAへの暗黙の変換を追加した場合、これはBがAのゼロデータ継承者であるかのように機能する可能性があると考えていましたしかし、これは明らかに疑問を投げかけます。これはC ++では未定義ですか?通常、違法にキャストされた構造体の要素にアクセスすることは定義されていないと思います。それは理にかなっている。ただし、あるタイプから別のタイプにreinterpret_castingし、そのポインターを渡してから、間に違法なことを何もせずに再度キャストするのは問題ないと思います。また、コンパイラが非仮想構造体メンバーを実装する方法は、関数を作成することだと思います

B::do_something(B* b, int arg)

そして、Bの適切な引数を使用してそれを呼び出します。これは、前のケースに還元されます。これは、私の疑わしい論理では問題ありません。したがって、実際にはreinterpret_castAである構造体で.do_somethingを呼び出しても問題ないと思います。

ただし、これは、C++標準が実際に問題について述べていることについては何も述べていません。それで何か助けはありますか?また、誰かがこれが実際にどれだけうまく機能するかについての情報を持っている場合(つまり、「これまでに作成されたすべてのコンパイラがこれを受け入れる」、または「これは少数のコンパイラでのみ機能する」)、それも役立ちますが、少し劣ります。

4

2 に答える 2

2

A*をB*にキャストしてから、もう一度A *にキャストすると、標準では大丈夫だと言われていると思います。static_castsではありませんが、それらはreinterpret_castsになります。

しかし、通常の解決策の何が正確に間違っているのでしょうか?

class B
{
private:
  A* ptr;
public:
  B(A* p) : ptr(p) {}
  void do_something(int arg) { do_something_with_A(ptr,arg); }
};

あなたのソリューションと同じくらい効率的で、いじくり回しが少ないようです。

于 2011-08-24T04:34:45.247 に答える
1

完全に無関係な2つのクラスタイプ間でstatic_castはできないため、使用している場合はこれが機能するとは思われません。static_cast具体的には、型のポインターがあり、A*それを型のポインターに変換しようとすると、この宣言が有効な場合B*にのみ成功します。static_cast

B* ptr(myAPtr);

または、Bが仮想的に派生しAていない場合(そうではありません)。この詳細については、ISO仕様§5.2.9を参照してください。上記の宣言を考慮すると、ここで§4のすべてに適用できる変換は§4.10の変換のみであり、適用できるのは基本クラスから派生クラスへの変換のみです(§4.10/ 3)。 、ただし、は関連するタイプではないためA、ここでは適用されません。B

ここで使用できる可能性のあるキャストは、のみでreinterpret_castあり、これも機能するようには見えません。特に、クラス階層間でのキャストの動作は(§5.2.10/ 7)です。

オブジェクトへのポインタは、異なるタイプのオブジェクトへのポインタに明示的に変換できます。65)タイプ「pointertoT1」の右辺値をタイプ「pointertoT2」に変換する場合を除きます(T1とT2はオブジェクトタイプであり、 T2のアラインメント要件がT1)のアラインメント要件よりも厳密ではなく、元のタイプに戻ると元のポインター値が生成される場合、そのようなポインター変換の結果は指定されていません。

したがって、2つのオブジェクトの配置制限が異なる場合、すぐに何かが機能するという保証はなく、これが正しいことを保証することはできません。しかし、あなたができるとしましょう。その場合でも、これは実際には正しく機能すると思います。これが理由です。オブジェクトのメンバー関数を呼び出すと、Bルール&5.2.2 / 1)が起動し、関数が非仮想であるため、次のように表示されます。

[...]メンバー関数呼び出しで呼び出される関数は、通常、オブジェクト式の静的タイプに従って選択されます。[...]

さて、少なくとも正しい関数を呼び出しています。さて、thisポインタはどうですか?まあ、&5.2.2 / 4によると:

[...]関数が非静的メンバー関数である場合、関数(9.3.2)の「this」パラメーターは、明示的な型変換(5.4)のように変換された、呼び出しのオブジェクトへのポインターで初期化されます。 )。[...]

最後の部分で行われる型変換は、選択された型であるため、aからaB*へのID変換です。B*したがって、thisポインタを適切に設定して適切な関数を呼び出しました。良い!最後にreinterpret_cast、元のタイプに戻すと、以前のルールにより、A*オブジェクトが返され、すべてが期待どおりに実行されます。

もちろん、これはオブジェクトが同じ配置要件を持っている場合にのみ機能し、これは保証できません。したがって、あなたはそれをすべきではありません!

お役に立てれば!

于 2011-08-24T04:45:34.203 に答える