74

C ++では、構造は、成功するために動的型の継承階層に表示される必要がある他のポインター型へのT q = dynamic_cast<T>(p);ポインターのランタイムキャストを実行します。それはすべて大丈夫です。pT*p

ただし、を実行することもできますdynamic_cast<void*>(p)。これにより、「最も派生したオブジェクト」へのポインタが返されます(C++11の5.2.7::7を参照)。この機能はおそらくダイナミックキャストの実装で無料で提供されることを理解していますが、実際には便利ですか?結局のところ、そのリターンタイプはせいぜいですvoid*、それでこれは何が良いですか?

4

7 に答える 7

70

dynamic_cast<void*>()、多重継承を処理している場合でも、実際にIDをチェックするために使用できます。

このコードを試してください:

#include <iostream>

class B {
public:
    virtual ~B() {}
};

class D1 : public B {
};

class D2 : public B {
};

class DD : public D1, public D2 {
};

namespace {
    bool eq(B* b1, B* b2) {
        return b1 == b2;
    }

    bool eqdc(B* b1, B *b2) {
        return dynamic_cast<void*>(b1) == dynamic_cast<void*>(b2);
    }
};

int
main() {
    DD *dd = new DD();
    D1 *d1 = dynamic_cast<D1*>(dd);
    D2 *d2 = dynamic_cast<D2*>(dd);

    std::cout << "eq: " << eq(d1, d2) << ", eqdc: " << eqdc(d1, d2) << "\n";
    return 0;
}

出力:

eq: 0, eqdc: 1
于 2011-11-14T15:48:38.947 に答える
7

C ++を使用すると、古いCの方法で処理を実行できることに注意してください。

型を介してオブジェクトポインタを密輸することを余儀なくされているAPIがあるとしますvoid*が、最終的に渡されるコールバックはその動的型を認識します。

struct BaseClass {
    typedef void(*callback_type)(void*);
    virtual callback_type get_callback(void) = 0;
    virtual ~BaseClass() {}
};

struct ActualType: BaseClass {
    callback_type get_callback(void) { return my_callback; }

    static void my_callback(void *p) {
        ActualType *self = static_cast<ActualType*>(p);
        ...
    }
};

void register_callback(BaseClass *p) {
   // service.register_listener(p->get_callback(), p); // WRONG!
   service.register_listener(p->get_callback(), dynamic_cast<void*>(p));
}

間違い!多重継承が存在する場合は失敗するため、コードは間違っています(また、継承がない場合でも動作することが保証されていません)。

もちろん、APIはあまりC ++スタイルではなく、から継承すると「正しい」コードでさえうまくいかない可能性がありますActualType。ですから、これがの見事な使用法であるとは言いませんがdynamic_cast<void*>、それは使用法です。

于 2011-11-14T16:08:15.240 に答える
4

ポインタをキャストすることvoid*は、C時代からずっと重要です。最も適切な場所は、オペレーティングシステムのメモリマネージャ内です。作成したもののすべてのポインタとオブジェクトを格納する必要があります。それをvoid*に格納することにより、オブジェクトをメモリマネージャのデータ構造に格納するように一般化しheap/B+Treeますarraylist

簡単にするためにlist、一般的なアイテムの作成の例を見てください(リストには完全に異なるクラスのアイテムが含まれています)。これは、を使用する場合にのみ可能void*です。

標準では、dynamic_castは不正な型キャストに対してnullを返す必要があり、標準は、関数ポインターを除いて、すべてのポインターがそれをvoid*に型キャストして元に戻すことができることも保証します。

通常のアプリケーションレベルの実際の使用法は、void*型キャストでは非常に少ないですが、低レベル/組み込みシステムで広く使用されています。

通常、低レベルのものにはreinterpret_castを使用する必要があります。たとえば、8086では、同じベースのポインターをオフセットしてアドレスを取得するために使用されますが、これに限定されません。

編集: 標準では、任意のポインタをに変換できるとされていますが、オブジェクトに戻すことができないとはどこにも示されていませんvoid*dynamic_cast<>void*

ほとんどの使用法では、一方通行ですが、避けられない使用法がいくつかあります。

dynamic_cast<>要求されたタイプに戻すには、タイプ情報が必要であるとだけ書かれています。

void*いくつかのオブジェクトに渡す必要がある多くのAPIがあります。java / Jniコードは、オブジェクトをとして渡しますvoid*
タイプ情報がないとキャストできません。要求された型が正しいことを十分に確信している場合は、コンパイラーにdynmaic_cast<>トリックを実行するように依頼できます。

このコードを見てください:

class Base_Class {public : virtual void dummy() { cout<<"Base\n";} };
class Derived_Class: public Base_Class { int a; public: void dummy() { cout<<"Derived\n";} };
class MostDerivedObject : public Derived_Class {int b; public: void dummy() { cout<<"Most\n";} };
class AnotherMostDerivedObject : public Derived_Class {int c; public: void dummy() { cout<<"AnotherMost\n";} };

int main () {
  try {
    Base_Class * ptr_a = new Derived_Class;
    Base_Class * ptr_b = new MostDerivedObject;
    Derived_Class * ptr_c,*ptr_d;

        ptr_c = dynamic_cast< Derived_Class *>(ptr_a);
        ptr_d = dynamic_cast< Derived_Class *>(ptr_b);

        void* testDerived = dynamic_cast<void*>(ptr_c);
        void* testMost = dynamic_cast<void*>(ptr_d);
        Base_Class* tptrDerived = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testDerived));
        tptrDerived->dummy();
        Base_Class* tptrMost = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testMost));
        tptrMost->dummy();
        //tptrMost = dynamic_cast<AnotherMostDerivedObject*>(static_cast<Base_Class*>(testMost));
        //tptrMost->dummy(); //fails

    } catch (exception& my_ex) {cout << "Exception: " << my_ex.what();}
    system("pause");
  return 0;
}

これがまったく正しくない場合は、私を訂正してください。

于 2011-11-16T19:14:37.973 に答える
1

ストレージをメモリプールに戻すときに便利ですが、基本クラスへのポインタのみを保持します。この場合、元のアドレスを把握する必要があります。

于 2011-11-20T08:35:24.823 に答える
1

@BruceAdiの回答を拡張し、この議論に触発されて、ポインターの調整が必要になる可能性のある多形の状況を次に示します。このファクトリタイプのセットアップがあるとします。

struct Base { virtual ~Base() = default; /* ... */ };
struct Derived : Base { /* ... */ };

template <typename ...Args>
Base * Factory(Args &&... args)
{
    return ::new Derived(std::forward<Args>(args)...);
}

template <typename ...Args>
Base * InplaceFactory(void * location, Args &&... args)
{
    return ::new (location) Derived(std::forward<Args>(args)...);
}

今私は言うことができます:

Base * p = Factory();

しかし、これを手動でクリーンアップするにはどうすればよいですか?呼び出すには実際のメモリアドレスが必要です::operator delete

void * addr = dynamic_cast<void*>(p);

p->~Base();              // OK thanks to virtual destructor

// ::operator delete(p); // Error, wrong address!

::operator delete(addr); // OK

または、メモリを再利用できます。

void * addr = dynamic_cast<void*>(p);
p->~Base();
p = InplaceFactory(addr, "some", "arguments");

delete p;  // OK now
于 2012-08-09T14:25:01.117 に答える
0

これは、ABIを介して不透明ポインターを提供する1つの方法である可能性があります。不透明(OPAQUE)ポインター(より一般的には不透明(OPAQUE)データ型)は、クライアントコードをライブラリの実装の詳細から分離できるように、ライブラリコードとクライアントコードの間でオブジェクトやその他のリソースを渡すために使用されます。確かに、これを達成する方法 にもありますが、特定のユースケースにはそれらのいくつかが適している可能性があります。

Windowsは、APIでOpaquePointersを多く使用しています。 HANDLEたとえば、一般的に、実際のリソースへの不透明なポインタであると私は信じていますHANDLEHANDLEsは、ファイル、GDIオブジェクト、およびさまざまな種類のあらゆる種類のユーザーオブジェクトなどのカーネルオブジェクトにすることができます。これらはすべて、実装が大幅に異なる必要がありますが、すべてがHANDLEユーザーに返されます。

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;


/*** LIBRARY.H ***/
namespace lib
{
    typedef void* MYHANDLE;

    void        ShowObject(MYHANDLE h);
    MYHANDLE    CreateObject();
    void        DestroyObject(MYHANDLE);
};

/*** CLIENT CODE ***/
int main()
{
    for( int i = 0; i < 25; ++i )
    {
        cout << "[" << setw(2) << i << "] :";
        lib::MYHANDLE h = lib::CreateObject();
        lib::ShowObject(h);
        lib::DestroyObject(h);
        cout << "\n";
    }
}

/*** LIBRARY.CPP ***/
namespace impl
{
    class Base { public: virtual ~Base() { cout << "[~Base]"; } };
    class Foo   : public Base { public: virtual ~Foo() { cout << "[~Foo]"; } };
    class Bar   : public Base { public: virtual ~Bar() { cout << "[~Bar]"; } };
};

lib::MYHANDLE lib::CreateObject()
{
    static bool init = false;
    if( !init )
    {
        srand((unsigned)time(0));
        init = true;
    }

    if( rand() % 2 )
        return static_cast<impl::Base*>(new impl::Foo);
    else
        return static_cast<impl::Base*>(new impl::Bar);
}

void lib::DestroyObject(lib::MYHANDLE h)
{
    delete static_cast<impl::Base*>(h);
}

void lib::ShowObject(lib::MYHANDLE h)
{
    impl::Foo* foo = dynamic_cast<impl::Foo*>(static_cast<impl::Base*>(h));
    impl::Bar* bar = dynamic_cast<impl::Bar*>(static_cast<impl::Base*>(h));

    if( foo ) 
        cout << "FOO";
    if( bar )
        cout << "BAR";
}
于 2011-11-22T18:11:09.327 に答える
0

家でそれをしないでください

struct Base {
    virtual ~Base ();
};

struct D : Base {};

Base *create () {
    D *p = new D;
    return p;
}

void *destroy1 (Base *b) {
    void *p = dynamic_cast<void*> (b);
    b->~Base ();
    return p;
}

void destroy2 (void *p) {
    operator delete (p);
}

int i = (destroy2 (destroy1 (create ())), i);

警告:次のように定義されている場合、これは機能しません。D

構造体D:ベース{
    void *演算子new(size_t);
    void演算子delete(void *);
};

そしてそれを機能させる方法はありません。

于 2011-11-23T08:58:58.647 に答える