web-dev-qa-db-ja.com

ポインタを無効にする動的キャストの実用的な使用法はありますか?

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

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

72
Kerrek SB

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
69
mitchnull

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

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

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*>の見事な使用法であるとは言いませんが、それは使用法です。

7
Steve Jessop

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 Codeは、オブジェクトを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;
}

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

4
Praveen

@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
1
Kerrek SB

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

1
BruceAdi

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

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が次のように定義されている場合、これは機能しません

 struct D:Base {
 void * operator new(size_t); 
 void operator delete(void *); 
}; 

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

0
curiousguy

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

Windowsは、APIでOpaquePointersを多く使用しています。 HANDLEは、一般的に、たとえばHANDLEがある実際のリソースへの不透明なポインタであると私は信じています。 HANDLEsは、ファイル、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";
}
0
John Dibling