【问题标题】:about void pointer, classes and casting关于 void 指针、类和强制转换
【发布时间】:2014-04-01 10:47:09
【问题描述】:

我有大约一两年的 C++ 经验,但我的编码方式与我在 Java 中编码的方式相同(简单的 oop 内容)。现在我有这个我不明白的示例代码。 (它很大所以我试着让它更短,我希望它对你们来说足够清楚)

//in .h file
typedef void*(*AnimalCreation)();

//in .cpp
void foo(void* p)
{
    AnimalCreation ac = (AnimalCreation)p;

    Animal* current_animal = reinterpret_cast<Animal*>(ac());
    current_animal->init();
}

//somewhere in another class foo is called
Dog* dog = new Dog(); //Dog is a subclass of Animal
foo((void*)&dog) 

AnimalCreation 的目的是什么? 那和有什么区别呢

typedef void(*AnimalCreation)();`//without asterisk after void

foo 内部发生了什么?

如果 foo 接收的预期参数始终是 Animal 的子类,为什么程序员需要像上面那样实现它而不仅仅是 foo(Animal*)?

谢谢。

【问题讨论】:

  • 我看不出这是如何工作的,因为foo 接收到一个指向对象的指针,并尝试将该指向对象的指针用作函数。
  • 对于AnimalCreation,它是指向不带参数并返回void * 的函数的指针的类型别名。当您删除该星号时,您将获得一个指向不带参数且不返回的函数的指针(它返回 void)。
  • 函数指针与void *is absolutely best avoided之间的转换。

标签: c++ casting void-pointers


【解决方案1】:
typedef void(*AnimalCreation)();

this 声明“AnimalCreation”用作指向不返回任何值的函数的指针的类型别名,而 this

typedef void*(*AnimalCreation)();

声明它用作指向返回 void 指针的函数的指针的类型别名,即指向您不知道其类型的东西的地址。

foo 内部,您正在接收这样的“通用地址”,并且您正在C 转换(可能不安全,在运行时检查)它到函数指针。这需要您自担风险:您不知道收到的地址指向什么。之后,您将调用该函数并接收另一个 void 指针,您将其重新解释(危险)为Animal 对象。然后你就可以使用它了。

函数指针不能是任何东西的子类,所以我不认为该代码中的参数是 Animal 子类...而是 Animal 类的子类是该函数返回的对象。假设它也是一个多态类,那么您将能够使用虚拟继承规则调用它的方法。如果您打算检查函数调用接收到的指针并且不确定它是否是Animal 类的子类,您宁愿使用dynamic_cast

附带说明:在 C++ 中,在函数指针和 void* 之间进行转换是一种不好的做法,因为您会丢失有价值的类型信息。

【讨论】:

  • 我在最后一部分的真正意思是 foo 实现中的参数是 foo(Animal*){...} 而不是 foo(void*){...},但是既然你提到它正在接收一个通用地址并重新解释它,这一切都是有道理的。谢谢。
  • 我不知道该代码,但我想出于某种原因,对象需要通过您收到的函数以某种方式实例化/创建/操作......类似于“工厂方法”。
【解决方案2】:

typedef 行是 AnimalCreation 被定义为 function pointer type

Function foo 接受一个 void * 参数,它转换为 AnimalCreation 类型(即转换为函数指针类型)。然后它可以通过函数指针调用该函数。此调用返回一个 void *(根据 typedef - 第一个括号之前的部分是返回类型,因此是 void*),然后通过 reinterpret_cast 将其强制转换为 Animal*。

如果您从 typdef 中删除星号 - 它仍然会声明函数指针类型,但现在返回值将是 void 而不是 void *(即没有返回,而不是指针)。您仍然可以通过函数指针调用该函数,但它不会返回任何内容。

总之,这是一个不错的函数指针小教程。

编辑:这段代码似乎在做什么的大图 - 这是在 C++ 中实现“工厂模式”的一种方式 - 抽象对象的创建,并将多态基类指针返回到派生类。在 void * 和函数指针和 reinterpret_cast 之间进行转换并不是实现这一目标的最佳方式,对于替代方案,您可以查看 here

【讨论】:

    【解决方案3】:

    首先,这是相当难看的 C 风格代码。

    typedef void*(*AnimalCreation)();
    

    要解释这一点,请遵循 C 和 C++ 声明阅读的一般规则:如果您将声明键入为表达式,您将获得它的类型。

    1. *AnimalCreation 这意味着 AnimalCreation 是一个指针
    2. (*AnimalCreation)() 这意味着*AnimalCreation 是一个不带参数的函数,所以AnimalCreation 是一个指向不带参数的函数的指针
    3. void *(*AnimalCreation)() 这意味着(*AnimalCreation)()void*(= 指向void 的指针),所以AnimalCreation 是一个指向不带参数并返回void* 的函数的指针。

    如果只是typedef void (*AnimalCreation)();,它将是一个指向不带参数且不返回值的函数的指针(即返回void)。

    现在,foo()

    这需要一个void*(指向任何东西的指针)并将其解释为AnimalCreation——作为一个指向不带参数并返回void*的函数的指针。如果传递给foo 的参数实际上是那种类型,那么一切都很好。如果传入其他内容,程序将显示 Undefined Behaviour, 这意味着任何事情都可能发生。它很可能会崩溃,因为它可能会尝试将数据解释为代码。

    foo() 调用传入的函数,它返回一个void*foo() 然后将其解释为指向 Animal 的指针。如果这就是函数实际返回的内容,那就太好了。如果没有,再次出现未定义行为。

    最后,您显示的调用将强制发生未定义行为,因为它传递了指向对象的指针的地址。但是,如上所述,foo() 会将其解释为函数的地址,并尝试调用该函数。欢闹随之而来。

    总而言之,这样的代码很糟糕,它的作者应该感觉很糟糕。您希望看到此类代码的唯一地方是与 C 样式外部库的互操作性,在这种情况下,它应该有非常好的文档记录。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-13
      • 2018-03-05
      • 1970-01-01
      • 2013-12-24
      • 2015-02-14
      • 2021-09-20
      • 2016-09-13
      相关资源
      最近更新 更多