74

在 C++ 中,T q = dynamic_cast<T>(p);构造执行一个指向p其他指针类型的指针的运行时强制转换,该指针类型T必须出现在动态类型的继承层次结构*p中才能成功。这一切都很好。

但是,也可以执行dynamic_cast<void*>(p),这将简单地返回一个指向“最派生对象”的指针(参见 C++11 中的 5.2.7::7)。我知道这个功能在动态转换的实现中可能是免费的,但它在实践中有用吗?毕竟它的返回类型充其量是void*,那这有什么好处呢?

4

7 回答 7

70

确实可以用于检查身份,dynamic_cast<void*>()即使处理多重继承。

试试这个代码:

#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,我被迫通过 type 走私一个对象指针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

早在 C 时代以来,将指针转换为void*就具有重要意义。最合适的地方是操作系统的内存管理器里面。它必须存储您创建的所有指针和对象。通过将其存储在 void* 中,他们将其概括为将任何对象存储到内存管理器数据结构中,该结构可以是heap/B+Tree或 simple arraylist

为简单起见,以创建一个list通用项目为例(列表包含完全不同类的项目)。只有使用void*.

标准说 dynamic_cast 应该为非法类型转换返回 null 并且标准还保证任何指针都应该能够将它类型转换为 void* 并从它返回,只有函数指针除外。

普通应用程序级别的实际使用很少用于void*类型转换,但它广泛用于低级/嵌入式系统。

通常,您希望将 reinterpret_cast 用于低级内容,例如在 8086 中,它用于偏移相同基址的指针以获取地址,但不限于此。

编辑: 标准表示您可以将任何指针转换为void*即使,dynamic_cast<>但它没有说明您不能将其转换void*回对象。

对于大多数用法,它是单向街道,但有一些不可避免的用法。

它只是说dynamic_cast<>需要类型信息才能将其转换回请求的类型。

有许多 API 需要您传递void*给某个对象,例如。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

不要在家里这样做

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* operator new (size_t);
    无效运算符删除(无效*);
};

并且没有办法让它工作。

于 2011-11-23T08:58:58.647 回答
0

这可能是通过 ABI提供不透明指针的一种方式。不透明指针——更一般地说,不透明数据类型——用于在库代码和客户端代码之间传递对象和其他资源,这样客户端代码可以与库的实现细节隔离开来。当然,还有其他 方法可以实现这一点,也许其中一些方法对于特定用例会更好。

Windows 在其 API 中大量使用了不透明指针。 HANDLE我相信,例如,通常是指向您拥有的实际资源的不透明指针HANDLEHANDLEs 可以是内核对象,如文件、GDI 对象和各种用户对象——所有这些在实现上都必须大不相同,但都作为 a 返回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 回答