从 void* 转换为 Base* 是任何 Derived<I>*

Cast to Base* from void* being any Derived<I>*

在我使用外部库允许我通过 void* 存储一些用户数据的情况下,我面临这样一种情况,我必须存储指向不同 Derived<I> 虚拟实例的指针从常见的 Base 类型继承(实际上,继承不同的派生类型而不是单个模板化类型;但这简化了问题)。

虽然存在类似的问题 "Conversion from void* to the pointer of the base class" and its duplicate,但答案假设确切的指向类型是已知的,而我使用该不透明指针的一些代码知道对象的实际(多态)类型是什么指出,有些则没有(甚至可能不是我的代码);但在这种情况下,指向的类型实际上继承自 Base 的谓词始终为真。在存储指针之前向上转换指针 不是 一个选项,因为在我的给定对象实例的用例中,多个 Derived<I> 可能存在于其层次结构中,因此无法向右向下投射 Derived<I>.

我已经通过以下代码(在 Linux x86_64、GCC 7.1.1 上)测试了使用 RTTI 从不透明指针获取类型信息的能力:

#include <cstdio>
#include <typeinfo>
#include <cxxabi.h>
struct Base {
  Base() { printf("Base is at %p\n", this); }
  virtual ~Base() {}
};
template<int I>
struct Derived : public virtual Base {
  Derived() { printf("Derived<%i> is at %p\n", I, this); }
  virtual ~Derived() {}
};
const char* demangle(const char *mangled) {
  return abi::__cxa_demangle(mangled, 0, 0, nullptr);
}
int main(int, char**) {
  Derived<1> d1;
  Derived<2> d2;
  void *unkPtr1 = &d1, *unkPtr2 = &d2;
  printf("unkPtr1 -> %s\n", demangle(typeid(*reinterpret_cast<Base*>(unkPtr1)).name()));
  printf("unkPtr2 -> %s\n", demangle(typeid(*reinterpret_cast<Base*>(unkPtr2)).name()));
  return 0;
}

我不确定在这种情况下 reinterpret_casting 是否安全,但它对 typeid 表达式有效。

可能的输出是

Base is at 0x7ffff9ee4478
Derived<1> is at 0x7ffff9ee4478
Base is at 0x7ffff9ee4480
Derived<2> is at 0x7ffff9ee4480
unkPtr1 -> Derived<1>
unkPtr2 -> Derived<2>

这表明对于任何指向虚拟 Base 派生类型的指针,有足够的类型信息可以知道我们在继承层次结构中的位置。

尝试dynamic_cast<Base*>(reinterpret_cast<Base*>(unkPtr1))显然是无效和不安全的(unkPtr1实际上是Derived<1>*),并且是空操作。

是否可以使用 dynamic_casts 或 RTTI 提供的工具以 相对 类型安全的方式获取指向 Base 的指针来自 Derived<I>-指向 void*s?

This shows there is sufficient type information to know where we are in the inheritance hierarchy for any pointer to a virtually Base-derived type.

肯定不会。它仅证明 "undefined behavior" 对于您的编译器恰好意味着 "do what you expected." 它证明您正在使用的实现为虚拟基 class 提供了与派生 class 相同的地址。在这种特殊情况下。

就 C++ 标准而言,唯一可以将 void* 转换回的 是转换为 [= 时的原始类型制作了 10=](或像 unsigned char* 这样的字节指针)。使用 reinterpret_cast、C 风格转换或其他任何东西都没有关系。如果原始指针是某种类型,则不能将其转换为基数 class.

好吧,你可以把它放在那里。但是你不能对指针做任何事情,除了将它转换回 void*.

Up-casting the pointer before storing it is not an option either since in my use case for a given object instance, multiple Derived<I> may exist in its hierarchy, making it impossible to down-cast to the right Derived<I>.

废话。发送代码必须知道类型是什么,因为它将它转换为 void*。因为它知道类型是什么,所以它也必须知道基类型是什么。所以这可能是一个问题的方式是:

  1. 如果您有多个基类型,并且发送代码不知道接收代码使用的是哪种基类型。

  2. 如果发送代码是一个模板并且除了需要发送它之外不知道关于类型的任何信息,但接收代码需要一个特定的基数 class .

所有这些情况实际上意味着您编写了 不连贯 代码。发送方或接收方两方之一不知道另一方期望的类型。如果发送方和接收方无法就传输类型达成一致,那么发送方没有办法以接收方可以理解的方式发送。

让您的代码连贯,问题就会消失。

不幸的是,这不起作用。使用 void * 是程序员告诉编译器他们知道底层类型是什么的方式,而 reinterpret_cast 是程序员 告诉 编译器类型是什么的方式.您没有立即注意到示例中错误的唯一原因是 类 没有数据,因此基指针与派生指针相同。一旦数据出现,事情就不会那么顺利了。例如,修改您的示例:

#include <cstdio>
#include <typeinfo>
#include <cxxabi.h>
#include <string>

struct Base {
    explicit Base(const int inInt) :
      test1(inInt)
    { printf("Base is at %p\n", this); }
    virtual ~Base() {}

    int test1;
};

template<int I>
struct Derived : public virtual Base {
    Derived(const int inInt) :
      Base(inInt),
      testStr("Derived string")
    { printf("Derived<%i> is at %p\n", I, this); }
    virtual ~Derived() {}

    std::string testStr;
};

const char* demangle(const char *mangled) {
  return abi::__cxa_demangle(mangled, 0, 0, nullptr);
}

int main(int, char**) {
  Derived<1> d1(1);
  Derived<2> d2(2);
  void *unkPtr1 = &d1, *unkPtr2 = &d2;
  Base *basePtr1 = reinterpret_cast<Base*>(unkPtr1);
  Base *basePtr2 = reinterpret_cast<Base*>(unkPtr2);
  Base *realBase1 = &d1;
  Base *realBase2 = &d2;
  printf("basePtr1 %p, realBase1 %p -> %s\n", basePtr1, realBase1, demangle(typeid(*basePtr1).name()));
  printf("basePtr2 %p, realBase2 %p -> %s\n", basePtr2, realBase2, demangle(typeid(*basePtr2).name()));

  printf("realBase1 %d\n", realBase1->test1);
  printf("realBase2 %d\n", realBase2->test1);

  // This may crash, or give bogus values, depending on the exact compiler output.
  printf("basePtr1 %d\n", basePtr1->test1);
  printf("basePtr2 %d\n", basePtr2->test1);

  return 0;
}

使用上面的代码我得到:

Base is at 0x7ffce4892ae8
Derived<1> is at 0x7ffce4892ac0
Base is at 0x7ffce4892aa8
Derived<2> is at 0x7ffce4892a80
basePtr1 0x7ffce4892ac0, realBase1 0x7ffce4892ae8 -> Derived<1>
basePtr2 0x7ffce4892a80, realBase2 0x7ffce4892aa8 -> Derived<2>
realBase1 1
realBase2 2
basePtr1 -460772648
basePtr2 -460772712

因此,使用正常的指向基址的指针,您可以获得正确的解引用值,但是跳过 void,您会得到虚假值。

我相信您唯一的选择是确保存储在 void * 中的指针属于 Base 类型。这样你的代码稍后运行,知道你有什么。通过某种形式的变体实现,例如 QVariantboost::any,您可能还会有一些运气。然后使用指向变体类型的 void * 指针。但是,您声明首先转换为 Base * 并不是一个真正的选项,因此我认为更改输入的类型无论如何都不是一个选项。

据我所知,您无法可靠地完成您想要的事情。您将不得不在某个地方做出妥协,可能需要 void * 来自 Base *