如何在通用情况下从void*指针动态转换类型?

6
class BASE {
public:
    virtual ~BASE() {}
    void lamp() {
        cout << "\nBASE CLASS";
    }
};

class DERIVED : public BASE {
public:
    void fun();
};

void DERIVED::fun() {
    cout << "\nDERIVED CLASS!";
}

int main() {

    BASE * pbase = new DERIVED; //BASE CLASS POINTER
    void * vbase = pbase;       //VOID POINTER TAKING BASE POINTER 
    DERIVED * pder;             //DERIVED CLASS POINTER

    //pder = static_cast<DERIVED *>(vbase);  //THIS WORKS
    pder = dynamic_cast<DERIVED *>(vbase);   //THIS DOESN'T
    pder->lamp();
    pder->fun();

    return 0;
}

每当我尝试将void*指针动态转换为派生类指针时,就会出现以下错误:

无法将'type 'void*'的'vbase'动态转换为'type 'class DERIVED*'(源不是指向类的指针)

我在StackOverflow上搜索并遵循建议,在基类中实现了虚函数以避免错误。我做错了什么?这种情况是否可能?
我的总体意图是使用void*指针将任何传入的对象类型转换为Derived类类型。我希望你能理解我的意思。
例如:
void dynamicCast(void * vptr)
{
    BASE * pbase = new DERIVED;
    DERIVED * pder;

    pder = dynamic_cast<DERIVED *>(vbase);
}

我应该能够将任何类型的指针传递给dynamicCast函数,并且它应该转换为派生类指针。


2
你可以将一个 void* 存储在列表中,不需要强制转换为其他类型。你需要一个类来存储这个 void*。 - Garr Godfrey
@GarrGodfrey 当我尝试访问存储在内存位置中的值时,出现错误。左边的->必须指向类/结构体/联合体/通用类型。 - Vignesh Gunasekaran
那是一个不同的错误。 - drescherjm
那就不要这样做。 - Garr Godfrey
1
不,这很容易。你只是不能窥视传递给你的数据。不要使用void*作为节点,它是附加到节点的数据。 - Garr Godfrey
显示剩余6条评论
4个回答

4
我认为存在设计或理解问题。
如先前所述,您无法从 void* 进行 dynamic_cast
为什么?因为 dynamic_cast 需要一些运行时类型信息(RTTI)来进行转换(有关详细信息,请参见 this link)。从 void* 单独来看,C++ 代码无法知道此信息的位置。最少需要使用一个具有此 RTTI 信息的对象指针。
此信息是创建并绑定到至少具有一个虚方法的任何类。如果没有虚方法,则不包括此信息。这就是为什么以下内容不起作用的原因:
struct A
{
};

struct B : A
{
};

int main()
{
  B  b;
  A *a = &b;

  dynamic_cast<B *>(a);  // YOUR COMPILE TIME ERROR
}

解决方法是向A添加一个虚方法。在这里,我已经添加了一个虚析构函数,如通常是一个好习惯

struct A
{
   virtual ~A() = default;
};

struct B : A
{
};

int main()
{
  B  b;
  A *a = &b;

  dynamic_cast<B *>(a);  // OK
}

请注意,dynamic_cast 允许您检查转换是否合法。
例如,我们可以添加一个C类并进行检查:
struct C 
{
};

int main()
{
  B  b;
  A *a = &b;

  assert(dynamic_cast<B *>(a)!=nullptr); // OK
  assert(dynamic_cast<C *>(a)==nullptr); // OK can not cast A to C 
}

这种运行时操作并非免费。如果您寻求最大速度,可以使用此技巧:
assert(dynamic_cast<B *>(a)!=nullptr);
B* b=static_cast<B*>(a);

在调试模式下,您将检查一切是否正常,并在发布模式下使用-DNDEBUG标志以删除assert,您只需使用static_cast

2
当您将指向对象类型的指针转换为指向void的指针时,该void指针的唯一有效转换是返回其原始类型。您无法对指向void的指针使用dynamic_cast,因为void不是多态类型,所以您需要使用static_cast来完成此操作。就像这样:
BASE *pbase = new DERIVED;
void *vbase = pbase; // Ok; implicit conversion to void*
BASE *pbase1 = static_cast<BASE*>(vbase); // the cast is required

一旦您已经获取了指向BASE的指针,您可以使用dynamic_cast将其转换为指向派生类型的指针:

DERIVED *pder = dynamic_cast<DERIVED*>(pbase1);

1
你不能在一个 void * 上使用 dynamic_cast
根据规范,对于 dynamic_cast<T>(v)

如果 T 是指针类型,那么 v 必须是一个指向完整类类型的 prvalue,并且结果是类型为 T 的 prvalue。...

你应该让所有的类都从同一个多态基类(至少有一个虚函数)BASE 派生,然后使用 BASE * 而不是 void *

这是给我的一个任务。我必须使用头文件实现各种链表功能。函数声明在头文件中,定义在单独的.cpp文件中。插入新节点的函数必须以void指针作为参数,因为用户可以在具有不同成员数量的文件中使用任何类型的对象作为节点,并在main()函数中调用该函数。希望你能理解我想说的话。简而言之,三个文件:一个包含函数声明的头文件,一个包含函数定义的.cpp文件,以及另一个实现函数的.cpp文件。 - Vignesh Gunasekaran
你需要访问链表中的void *吗,还是只有链表的用户需要知道其中的内容? - fefe
但是 void *vbase = pbase。它有基础指针,对吗?还是我错了? - Vignesh Gunasekaran
如果您的链表需要访问void内的某些函数,则将它们放在一个基类中,并使所有内容都从该基类继承。您不能在此处使用void,必须使用base。否则,列表的用户需要跟踪void并使用适当的转换将其转换回来,但不是dynamic_cast。 - fefe
1
类型信息在C++中非常重要,使用void*会丢失它。它们可能具有相同的内部值,但行为是不同的。 - fefe
显示剩余4条评论

1
您的通用链表应该像这样:

class Node
{
    Node* next;
    void* vdata;
}

那是唯一必需的数据结构,但您可能需要一个循环列表,或者一个基节点来跟踪列表的末尾,或者一个双向链表。
调用者向您传递void*,您创建一个新节点,设置“vdata”并将节点添加到您的列表中。

1
注意,调用方必须知道何时释放指向void*的数据。 - Garr Godfrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接