动态强制转换空指针有实际用途吗?

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

但是,也可以执行 dynamic_cast<void*>(p),它只返回一个指向“最派生对象”的指针(参见 C + + 11中的5.2.7: : 7)。我理解这个特性在动态强制转换的实现中可能是免费的,但是它在实践中有用吗?毕竟,它的返回类型最多是 void*,那么这有什么好处呢?

4954 次浏览

即使在处理多重继承时,也可以使用 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

请记住,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*>的一个绝妙用法,但它确实是一种用法。

早在 C 时代,向 void*施加指针就有其重要性。 最合适的位置是在操作系统的内存管理器内。它必须存储您创建的所有指针和对象。通过将其存储在 void * 中,他们将其推广为将任何对象存储到内存管理器数据结构中,这些数据结构可以是 heap/B+Tree或简单的 arraylist

为简单起见,以创建一个泛型项的 list为例(List 包含完全不同类的项)。只有使用 void*才能做到这一点。

标准规定 Dynamic _ cast 应该为非法类型强制转换返回 null,并且标准规定任何指针都应该能够将其类型强制转换为 void * 并从其返回,只有函数指针例外。

void*类型转换的一般应用级实际应用非常少,但它在低级/嵌入式系统中得到了广泛的应用。

通常,您会希望对低级别的内容使用 rerelease _ cast,比如在8086中,它用于偏移相同基数的指针以获取地址,但不限于此。

编辑: 标准说,你可以转换任何指针到 void*,甚至与 dynamic_cast<>,但它没有任何地方的状态,你不能转换的 void*回到对象。

对于大多数使用,这是一个单行道,但有一些不可避免的使用。

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

有许多 API 要求您将 void*传递给某个对象,例如 java/Jni 代码将该对象作为 void*传递。
如果没有类型信息,就不能执行 casting.如果你有足够的信心,要求的类型是正确的,可以通过一个技巧让编译器执行 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;
}

如果有任何不正确的地方,请纠正我。

当我们将存储放回内存池时,它是有用的,但是我们只保留一个指向基类的指针。这个案子我们应该找出原始地址。

这可能是通过 ABI 提供 不透明指针的一种方法。不透明指针(以及更一般的 不透明数据类型)用于在库代码和客户机代码之间传递对象和其他资源,以便客户机代码可以与库的实现细节隔离。可以肯定的是,有 其他方式可以完成这个任务,而且其中一些可能对于特定的用例更好。

Windows 在其 API 中大量使用了不透明指针。例如,我相信 HANDLE通常是指向实际资源的不透明指针。HANDLE可以是内核对象,比如文件、 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";
}

别在家这么做

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*);
};

没有办法让它运作。

扩展@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