动态投射到空指针是否有实际用途?


73

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

但是,也可以执行dynamic_cast<void*>(p),它将仅返回指向“最派生对象”的指针(请参见C ++ 11中的5.2.7 :: 7)。我知道此功能可能在动态演员表的实施中免费提供,但实际上有用吗?毕竟,它的返回类型充其量void*是什么,那么这有什么用呢?


7
只是一个猜测,但这不能用于明确确定对象身份吗?
比约恩博动

@BjörnPollex:但是这样会p......有一种情况p1 == p2,但dynamic_cast<void*>(p1) != dynamic_cast<void*>(p2)
Kerrek SB 2011年

8
啊,我明白您的意思:我们可以p1 != p2,但实际上它们指向同一个对象。我想我们是否有索引作为索引void *是有意义的。(尽管void指针本身将不再可用。)
Kerrek SB 2011年

我收到“错误:无法将ptr类型的Base *强制转换为void *(源类型不是多态的)。” 当我尝试使用该强制转换编译代码时-它在您的系统上工作吗?很抱歉删除原始评论-我想再次检查:)
John Humphreys-w00te 2011年

2
@BjörnPollex:您应该对评论做出回应,这听起来像是一个合理的主意,当然值得发表。
Kerrek SB 2011年

Answers:


69

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

我之所以会悬赏这个问题,主要是因为我觉得其他答案都没有增加什么新的意义。那不是最初的意图,但我不想浪费赏金。如果有人提出了新的好答案(或编辑现有答案),我将很乐意再发起一次“奖励”赏金活动!
Kerrek SB 2011年

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*>,但这是使用。


3
好吧,但是my_callback可以说dynamic_cast<ActualType*>(static_cast<BaseClass*>(p)),非吗?换句话说,我们可以将其BaseClass*用作C包装程序的基础。
Kerrek SB 2011年

@Kerrek:是的,我想你可以。为此,您可以添加另一个虚函数BaseClass::get_this_for_callback,并让每个派生类完全控制如何打包指针。
史蒂夫·杰索普

@SteveJessop:很好奇,如果p通过void*in返回,那么在存在多重继承的情况下,my_callback如果将其强制p转换为派生最多的类型作为第二个参数,为什么会很重要register_listener?我问是因为my_callback您在内部对astatic_cast进行操作ActualType*,所以指向最派生类型的指针或指向基类的指针是否过去my_callback……似乎都没有关系。成为ActualType对象的指针,对吗?
杰森

@杰森:不。在具有多重继承的情况下,并假设某些基址不为空,则至少一个基址的地址与最衍生对象的地址不同。如果输入是对象的地址,则从静态转换void*ActualType*只会产生正确的指针值ActualType,因此,如果BaseClass碰巧是位于不同地址的基址,则它将出错。
史蒂夫·杰索普

@Jason即使没有MI:从铸造void*T*才有效,如果值是从哪里来T*void*Derived*Base*void*Derived*有未定义行为(可能不大概MI工作)。
curiousguy

4

void*自从C天以来,将指针投射到它的重要性。最合适的位置是在操作系统的内存管理器中。它必须存储您创建的所有指针和对象。通过将其存储在void *中,他们将其通用化以将任何对象存储在内存管理器数据结构中,该对象可以是heap/B+Tree简单的arraylist

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

standard表示,dynamic_cast对于非法类型转换应返回null,而standard还保证任何指针都应能够将其类型转换为void *,并从其返回,只有函数指针例外。

正常的应用程序级别实际使用情况很少用于void*类型转换,但已广泛用于低级别/嵌入式系统中。

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

编辑: 标准表示您可以将任何指针转换为void*偶数,dynamic_cast<>但是没有位置指出您不能将其转换void*回该对象。

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

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

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

如果这在任何方面都不正确,请纠正我。


+1你对那几个链接,我很感兴趣,这样的“低层次的东西”

code.google.com/p/c-generic-library此链接包含用于通用数据结构的代码。
Praveen

2
您无法根据C ++标准第5.2.7 / 2节dynamic_cast对avoid*进行操作...因此,即使是通过操作生成的,dynamic_cast用于恢复void*通用数据结构中a的类型也不起作用。执行强制转换的指针必须是指向完整类类型的指针。void*dynamic_cast<void*>
杰森

确实。恐怕这个答案有些失误了。我知道一般的空指针。但是,问题特别是dynamic_cast关于指向多态类的指针。
Kerrek SB 2011年

确实,这dynamic_cast<>是一条街,但是如果您确定对象的类型信息,则可以从中取回对象。除此之外,除了比较指针之外,没有类型信息就没有可移植的用法。但这是语言功能,因为有可能这样做,语言只能向您保证其使用。相同reinterpret_cast<>
Praveen

1

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


嗯...您能详细说明一个有用的情况吗?
Kerrek SB 2011年

现在,这对我来说更有意义:您可以p->~T();通过诸如这样的提示工厂说出新的位置T::create_inplace(dynamic_cast<void*>(copy_of_p));
Kerrek SB 2012年

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

dynamic_cast在调用析构函数之后使用可能不是一个好主意。
Tadeusz Kopec

倒数第二个代码段:析构函数,::operator delete基于动态强制转换的结果。幸运的是,它很容易修复。
Tadeusz Kopec

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

而且没有办法使其起作用。


@SteveJessop好吧,我已经发布了重要的警告消息。
curiousguy

0

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

Windows在其API中大量使用了不透明指针。 HANDLE我相信,例如,通常它是指向您要使用的实际资源的不透明指针HANDLEHANDLEs可以是内核对象,例如文件,GDI对象以及各种类型的各种User Objects-所有这些在实现上都必须有很大的不同,但是所有的对象都将作为返回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";
}

有趣-但您确定static_cast<Base*>(dynamic_cast<void*>(pointer_to_base))正确吗?例如,生成的指针不能再动态地投射到空指针,并且实际上它不再可用。
Kerrek SB 2011年

dynamic_cast<void*>(new impl::Foo)拥抱?
curiousguy

@Kerrek:我很确定代码正确,并且不显示UB。例如,请参见:5.2.9 / 10:“可以将类型为“指针指向cv1无效”的右值转换为类型为“指针指向cv2 T的右值”,其中T是对象类型,而cv2是相同的cv-qualification作为或大于cv1的cv资格。类型为object的指针的值将转换为“ cv void的指针”并返回到原始指针类型,将具有其原始值。
John Dibling 2011年

@JohnDibling:不过,我认为我设法使该构造崩溃了。想象一下:如果您有一个Base * p;指向派生对象x,则static_cast<Base*>(dynamic_cast<void*>(p))与相同reinterpret_cast<Base*>(&x),而不是 static_cast<Base*>(&x)
Kerrek SB 2011年

@Kerrek:右键-你需要dynamic_castBase*高达(下笑?) Derived*,并随后得到了void*
约翰Dibling
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.