以下代码适用(在ideone上,Borland BCB 6和gcc)
#include <stdio.h>
class Base {
private:
Base **BPtr;
public:
Base(void *Ptr) {
BPtr = (Base **)Ptr;
*BPtr = this;
}
virtual ~Base() {
if(BPtr) *BPtr = NULL;
printf("Base aufgelöst\n");
}
};
class Child : public Base {
public:
Child(void *Var) : Base(Var) {}
~Child() {
printf("Child aufgelöst\n");
}
};
int main() {
Child *Ptr = NULL;
new Child(&Ptr);
printf("Childptr: %p\n", Ptr);
delete Ptr;
printf("Childptr: %p\n", Ptr);
return 0;
}
以上程序的输出是:
Childptr: 0x9cc0008
Child aufgelöst
Base aufgelöst
Childptr: (nil)
这正是我所期望和想要的。
我的问题很简单:这实际上是安全的还是只是看起来有效?因为首先隐式地将Child
类型的指针强制转换为void *
然后将其转换为Base **
或者是否存在任何明显(或隐藏)问题,确实感觉有点hacky此?
非常感谢。
编辑: 因为对我的意图似乎有点误解:
这个'hack'的唯一目的是NULL
有问题的变量,一旦对象被销毁以保护自己不会意外忘记手动变量NULL并可能在以后访问无效内存。
该变量必须且不会用于访问类的任何其他功能。
然而,我之前尝试(和工作过)的内容如下:
void FreeFunc(void *Ptr) {
if(Ptr) {
Base **BPtr = (Base **)Ptr;
delete *Ptr; //Calls Destructors
*Ptr = NULL;
}
}
到目前为止,我在这里得到的回复似乎更糟糕了?
答案 0 :(得分:1)
将儿童*投射到基地*是安全的。将儿童**投入基地**不是因为那些是无关的。这样做的问题是,如果我转换为void *然后转换为Base **,我现在可以将Base *分配给Derived *类型的指针,如下所示:
#include <iostream>
class Base
{
public:
virtual void Foo() { std::cout << "Base::Foo" << std::endl; }
};
class Child : public Base
{
public:
virtual void Foo() { std::cout << "Child::Foo" << std::endl; }
virtual void Bar() { std::cout << "Child::Bar" << std::endl; }
};
void main()
{
Child *d = new Child();
Base *b = d;
Child **d2 = &d;
Base **b2 = (Base **)(void *)d2; // Force it
*b2 = new Base(); // Whoops! Child *d now points to an instance of Base *
d->Bar();
}
答案 1 :(得分:0)
如果出现以下情况:
答案 2 :(得分:0)
你可以检查你的函数的void *指针是否可以使用dynamic_cast<Child*>
c++ function强制转换为Base *并检查结果是否等于0(不能转换)。
但是dynamic_cast效率不高,演员阵容非常慢。
答案 3 :(得分:0)
从Base*
转换为Derived*
反之亦然可以更改值中的位 - 您无法安全地从Base**
转换为Derived**
。理论上同样适用void*
,但在实践中不太可能。
存储void**
比稍微存储Base**
更有可能。仍未定义。
来自void*
的来回转换仅在您往返于完全相同的类型时才有效。
当您只是更改类型时地址更改的最常见情况是具有多重继承:
struct Base1 { char x; };
struct Base2 { char y; };
struct Derived: Base1, Base2 {};
在这种情况下,Derived* d
指向与Base1
相同的地址,但Base2
之后是。指向Base2
Derived
子对象的指针将具有不同的二进制地址。
在C ++中,有一些情况可以将指向A的指针重新解释为指向B的指针。这两个是布局兼容的时候。仅当所讨论的类型是普通旧数据/标准布局,并且基本上一个类是另一个类的前缀时,才会发生兼容布局。在大多数其他情况下,编译器几乎可以自由地重新排列事物。
充其量你可以深入研究你的特定实现的对象布局,并希望你没有错过任何东西。在实践中,通常最好使用类型擦除的驱逐舰之类的东西,而不是冒险解决问题:存储void**
和指向函数的指针,该指针知道它是从哪种类型转换而来,并将其转换回来那个。
struct cleanup_stuff {
void* pvoid;
void(*pfunc)(void*);
void operator()() const {
pfunc( pvoid );
}
};
template<class T>
cleanup_stuff clear_pointer( T** ptr ) {
return {
static_cast<void*>(ptr),
[](void* p){
T** ptr = static_cast<T**>(p);
*ptr = nullptr;
}
};
}
这里我们有cleanup_stuff
结构,用于存储void*
和对void*
的操作。我们的clear_pointer
接受一个指向指针的指针,并创建一个cleanup_stuff
,用于存储所述指针在稍后的时间点的清除。
在cleanup_stuff
中存储Base
,并将其指针作为模板T**
。在析构函数中,执行cleanup_stuff
。这会对您的解决方案产生额外的指针开销,并且不会执行未定义的行为。