假设我有以下代码,是否可以安全使用?
基类:
class B
{
public:
B(bool isDerived = false) : m_isDerived(isDerived) {}
bool isDerived() { return m_isDerived; }
private:
bool m_isDerived;
}
派生类:
class D : public B
{
public:
D() : B(true) {}
}
代码:
B* b = new B(); // Create new base class
D* unknown = static_cast<D*>(b); // Cast the base class into a derived class
if (unknown->isDerived()) // Is this allowed?
// unknown is a D and can be used as such
else
// unknown is not a D and can not be used
我可以安全地调用unknown-&gt; isDerived(),即使在这种情况下未知真的是B吗?
我们假设未知的NEVER包含除B *或D *以外的任何东西,并且我们永远不会做任何未知的事情,直到检查了isDerived()为止。
修改:
考虑到这些问题,我将尝试解释为什么我要尝试这样做: 所以基本上我有一个Windows树控件当然不能直接连接到我用来存储我的数据的c ++树结构。所以我必须将我的数据重新解释为一个DWORD_PTR,它与树控件中的每个节点一起存储,所以我在两者之间有一个连接。我的树结构由基本类型(普通节点)或派生类型(其中包含更多信息的节点,应该以不同方式处理)组成。指向这些的指针是reinterpret_cast:ed并放在树形控件中。
现在,当我单步执行树控件时,我想对派生类型的节点进行操作,因此我想将DWORD_PTR重新解释为一个派生类型。但为了完全正确,我应该首先将它重新解释为基类型(我猜?),然后如果它是派生类型,则将其向下转换为派生类型。但是我想我可以通过reinterpret_cast立即将它变成派生类型,然后通过函数检查它是否真的是派生类型。如果不是我不做任何事情。在我看来,指针中的基类数据应该位于相同的内存位置,无论它有多少派生,但我可能错了,这就是我在这里问的原因。
我想通过不涉及Windows来使问题更清楚,但我想要的实际情况将更接近于此:
B* b1 = new B();
B* b2 = new D();
DWORD_PTR ptr1 = reinterpret_cast<DWORD_PTR>(b1);
DWORD_PTR ptr2 = reinterpret_cast<DWORD_PTR>(b2);
D* unknown = reinterpret_cast<D*>(ptr1 /* or ptr2 */); // Safe?
unknown->isDerived(); // Safe?
基本上无论我做什么,它在某种程度上仍然不安全,因为我必须重新解释数据。
答案 0 :(得分:1)
我是否可以安全地调用unknown-&gt; isDerived(),即使未知真的是 在这种情况下B?
首先,你为什么要这样做?您可以拨打b->isDerived()
,然后进行向下转发。
现在,虽然过早且可能无效的向下转换会产生一种未定义的行为(并且应该被普遍鄙视),但在这种情况下它应该有效。 B和D都没有隐式数据成员可能会更改m_isDerived
的相对偏移量,并且isDerived
成员函数的地址是常量。
所以是的,它应该工作。如果 对您来说足够好。
编辑:您可以进行一些测试以确保偏移量相同:
#include <cstddef> // for offsetof macro
#include <cassert> // for assert
#define offsetofclass(base, derived) ((static_cast<base*>((derived*)8))-8)
class Test
{
public:
Test()
{
assert(offsetofclass(B, D) == 0);
// you'll need to befriend Test with B & D to make this work
// or make the filed public... or just move these asserts
// to D's constructor
assert(offsetof(B, m_isDerived) == offsetof(D, m_isDerived));
}
};
Test g_test;
这将在启动时执行。我不认为它可以变成静态断言(执行和编译时)。
答案 1 :(得分:1)
鉴于你的
class D : public B
和
B* b = new B(); // Create new base class
D* unknown = static_cast<D*>(b);
形式上这是未定义的行为,但是,只要只访问B
个东西,就没有技术上的原因它为什么不起作用。通常情况下,这样做是为了访问其他无法访问的B
内容,例如std::stack<T>::c
。但是有更好的方法,包括成员函数指针技巧。
关于
B(bool isDerived = false) : m_isDerived(isDerived) {}
非常脆弱且不安全。
而类B
应该有一个虚拟成员。然后,您可以使用dynamic_cast
和typeid
。这通常称为 RTTI ,运行时类型信息。
然而,在第三方并且抓狂的时候,应该通过课程B
提供相关功能,这样就不需要向下转发。
答案 2 :(得分:1)
正如您自己指出的那样,在单个不透明指针中存储任意层次结构的唯一通常正确的方法是通过基类。所以,首先制作你的层次结构:
struct Base { virtual ~Base(){}; /* ... */ };
struct FooDerived : Base { /* ... */ };
struct BarDerived : Base { /* ... */ };
struct ZipDerived : Base { /* ... */ };
现在,您将在Base*
和您拥有的任何原始指针类型之间进行独占转换。严格来说,您只能在void*
或uintptr_t
中存储指针,但我们假设您的DWORD_PTR
足够宽。
如果我们将所有内容都包含在一个函数中,那么已经完成了向上转换:
void marshal_pointer(Base const * p, DWORD_PTR & dst)
{
static_assert(sizeof(DWORD_PTR) == sizeof(void *), "Not implementable");
dst = reinterpret_cast<DWORD_PTR>(p);
}
返回方向同样简单:
Base * unmarshal_pointer(DWORD_PTR src)
{
static_assert(sizeof(DWORD_PTR) == sizeof(void *), "Not implementable");
return reinterpret_cast<Base *>(src);
}
如果可能,应该根据虚函数实现所有实际的多态行为。手动dynamic_cast
应该是你的最后手段(虽然偶尔它们是合适的):
Base * p = unmarshal_pointer(weird_native_windows_thing);
p->TakeVirtualAction();