从指向子变量的指针转换为指向父变量的指针是否安全?

时间:2014-10-14 11:07:55

标签: c++ pointers

以下代码适用(在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;
    }
}

到目前为止,我在这里得到的回复似乎更糟糕了?

4 个答案:

答案 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)

如果出现以下情况:

  1. 任何Base函数调用其他成员函数(其中任何一个) BaseChild?虚拟或非虚拟。
  2. void *指向Child*的保证是什么? 例如:没有什么可以阻止我们写在主要:

    void *pv; new Child(pv);

  3. 这些应该指出不能保证这种类型演员。

    如果我们确实需要类型转换基础&lt; - &gt;派生,RTTI是机制。 另请参阅wiki RTTI

答案 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。这会对您的解决方案产生额外的指针开销,并且不会执行未定义的行为。