1

私は最近、C++ の知識を更新/更新しており、厳密なエイリアシングについて学ぶことで、ある型のポインターを別の型にキャストすることに少し警戒するようになりました。次のコード サンプルが私のコンパイラで実際に機能することはわかっていますが、現在の標準に準拠していることを確認したいと思います。

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*(lookupTable[i]));
    }

    private:
    Member1 member1;
    Member2 member2;

    static Base Tuple::* const lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
Base Tuple<Base, Member1, Member2>::* const Tuple<Base, Member1, Member2>::lookupTable[2] = {
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member1),
    reinterpret_cast<Base Tuple<Base, Member1, Member2>::*>(&Tuple::member2)
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}

基本的に、この単純なタプルには要素のペアが含まれており、各要素は共通の基本クラスから派生する必要があります。Get 関数は、Base*指定されたインデックスが表すメンバーに を返します。

私が疑問に思っている重要な部分は、reinterpret_casts です。Derived Struct::*to からto へのキャストBase Struct::*一般的に禁止されていることはわかっていますが、この場合は、ポインターからメンバーへの変数のみを使用して、オブジェクトへのポインターを取得します。(派生オブジェクトをベース オブジェクトであるかのようにコピーしたり、ベース オブジェクトを派生オブジェクトのメモリに詰め込もうとしたりしません。) これは G++ で意図したとおりに機能し、そうでないことを確認したいだけです。これを行うために、準拠しているコンパイラに噛まれます。

4

4 に答える 4

2

そこでは reinterpret_cast を使用しないでください。実際、目標が移植性である場合は、 reinterpret_cast の使用について言及するべきではありません。reinterpret_cast は、定義上、プラットフォーム固有の結果を持つものです。

ベースへのポインターを派生クラスのポインターにキャストするには、dynamic_cast を使用します。ポイントされたオブジェクトが派生クラスでない場合、NULL が返されます。クラスが正しいと確信している場合は、static_cast を使用できます。

于 2011-01-03T06:06:12.860 に答える
1

の使用reinterpret_castはほとんど移植性がありません。その上、メンバ キャストへのポインタの唯一の有効な使用法は、 from からへの暗黙的なキャストと、 fromへType Derived::*Type Base::*慎重な使用です。メンバーを含むオブジェクトのタイプではなく、メンバーのタイプを変更したいので、これはそれらのどちらでもありません。static_castType Base::*Type Derived::*

メンバーへのポインターの代わりに、その配列に小さな関数を入れるのはどうですか? 次のコードはテスト済みで、完全に移植可能です。

#include <iostream>

using namespace std;

class MyBase {

    public:
    virtual void DoSomething() = 0;

};

class MyDerived1 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #1" << endl;
    }

};

class MyDerived2 : public MyBase {

    public:
    virtual void DoSomething() {
        cout << "I'm #2" << endl;
    }

};

template <typename Base, typename Member1, typename Member2>
struct Tuple {

    public:
    Base* Get(int i) {
        return &(this->*lookupTable[i])();
    }

    private:
    Member1 member1;
    Member2 member2;

    template <typename MemType, MemType Tuple::*member>
    Base& GetMember() { return this->*member; }

    typedef Base& (Tuple::*get_member_func)();
    static const get_member_func lookupTable[2];

};

template <typename Base, typename Member1, typename Member2>
const typename Tuple<Base, Member1, Member2>::get_member_func
Tuple<Base, Member1, Member2>::lookupTable[2] = {
    &Tuple::GetMember<Member1, &Tuple::member1>,
    &Tuple::GetMember<Member2, &Tuple::member2>
};

int main() {

    Tuple<MyBase, MyDerived1, MyDerived2> tuple;

    tuple.Get(0)->DoSomething();
    tuple.Get(1)->DoSomething();

    return 0;

}
于 2011-01-03T21:08:34.847 に答える
0

編集: 標準からの参照。どちらの例外も満たしていないため、正しく読んでいる場合、実行したことは特定されていないため、特定のコンパイラで動作する場合と動作しない場合があります。関連するメンバーのタイプに例外はありません。

5.2.10/9 から (reinterpret_cast):

「T1 型の X のメンバーへのポインター」型の右辺値は、T1 と T2 が両方とも関数型または両方のオブジェクト型である場合、「T2 型の Y のメンバーへのポインター」型の右辺値に明示的に変換できます。メンバー ポインター値 (4.11) は、変換先の型の null メンバー ポインター値に変換されます。次の場合を除いて、この変換の結果は規定されていません。

— 「メンバー関数へのポインター」型の右辺値をメンバー関数型への別のポインターに変換し、元の型に戻すと、メンバー値への元のポインターが生成されます。

— 「T1 型の X のデータ メンバーへのポインター」型の右辺値を「T2 型の Y のデータ メンバーへのポインター」型 (T2 のアラインメント要件は T1 のアラインメント要件より厳密ではない) に変換し、元の値に戻します。元の型は、メンバー値への元のポインターを生成します。

于 2011-01-03T06:28:35.933 に答える
-1

C スタイルのキャストは常に reinterpret_cast より優れています。

C スタイルのキャストが機能する場合、プラットフォーム単独で有効です。

reinterpret_cast は常に避けてください。

編集済み

つまり、reinterpret_cast を使用すると、間違ったメモリ アドレスを指す可能性があり、C スタイルのキャストは、ABI、メモリ アライン、ポインタ サイズなどのプラットフォーム関連の問題をすべて処理します。

EDITED コメンテーターのインスピレーションにより、ISO/IEC 14882:2003 セクション 5.2.10 "Reinterpret_cast" を読みました。

もちろん、私の理解は限られていますが、そもそも reinterpret_cast が嫌いだった理由を思い出すと、私は驚かされます。

reinterpret_cast には、継承階層が欠けているか、認識が非常に限られていると思います。

キャスト オペランドが複雑な継承階層を持つクラス (ATL/COM クラスなど) のインスタンス ポインターである場合、1 回の reinterpret_cast で理解できないエラーでプロセスを強制終了できます。

実際のキャスト操作に関する漠然とした知識があれば、C スタイル キャストを使用できます。しかし、reinterpret_cast を安全に使用するには、正確な詳細を知る必要があります。

于 2011-01-03T06:37:26.873 に答える