从void *转换为Base *为任何派生<i> *

时间:2017-08-26 01:40:16

标签: c++ casting polymorphism virtual-inheritance

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

虽然存在类似的问题"Conversion from void* to the pointer of the base class"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_cast在这种情况下是否是任何类型的安全,但它在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_cast或RTTI提供的工具从{{1}获取指向Base的指针} -pointing Derived<I> s?

2 个答案:

答案 0 :(得分:2)

  

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

肯定不是。它只能证明&#34;未定义的行为&#34;因为你的编译器碰巧意味着&#34;做你期望的事。&#34;它证明了您使用的实现为虚拟基类提供了与派生类相同的地址。在这种特殊情况下。

就C ++标准而言,唯一可以将void*强制转换为转换为void*时的原始类型(或类似unsigned char*的字节指针)。如果你使用reinterpret_cast,C风格的演员表等等,这并不重要。如果原始指针是某种类型,则无法将其强制转换为基类。

嗯,你可以把它扔在那里。但你不能对指针做任何事情,除非把它重新投射回void*

  

在存储指针之前向上转换指针也不是一个选项,因为在我的用例中,对于给定的对象实例,其层次结构中可能存在多个Derived<I>,因此无法向右转向{ {1}}。

无意义。发送代码必须知道类型是什么,因为它将其转换为Derived<I>。因为它知道类型是什么,所以它也必须知道基类型是什么。所以这可能是一个问题的方式是:

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

  2. 如果发送代码是一个模板,并且除了需要发送它之外,对该类型一无所知,但接收代码需要特定的基类。

    < / LI>

    所有这些案例实际上都意味着您已经编写了不连贯的代码。发件人或收件人这两方中的一方对另一方所期望的类型一无所知。如果发送方和接收方无法就透射率类型达成一致,那么发送方无法 以接收方可以理解的方式发送它。

    让你的代码连贯一致,问题就消失了。

答案 1 :(得分:0)

不幸的是,这不起作用。使用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 *