0

The following code works (on ideone, Borland BCB 6 and 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;
}

The output of above program is:

Childptr: 0x9cc0008
Child aufgelöst
Base aufgelöst
Childptr: (nil)

which is exactly what I expected and want.

My question is simple: Is this actually safe or does it only seem to work? Because it does feel a little bit hacky to first implicitly cast a Pointer of type Child to a void * and then cast it to a Base ** or are there any obvious (or hidden) problems with this?

Thank you very much.

Edit: Since there seemed to a bit of a misunderstanding regarding my intention:

The sole purpose of this 'hack' is to NULL the variable in question, once the object gets destroyed to protect myself against accidentally forgetting to NULL the variable manually and possibly accessing invalid memory later on.

The variable must and will not be used to access any other functionality of the classes.

However, what I also tried (and worked) before was the following:

void FreeFunc(void *Ptr) {
    if(Ptr) {
        Base **BPtr = (Base **)Ptr;
        delete *Ptr;    //Calls Destructors
        *Ptr = NULL;
    }
}

Which appears to be even worse from the replies I had been getting on here so far?

ATaylor
  • 2,598
  • 2
  • 17
  • 25
  • No, it's not safe; it's undefined. That it seems to work is just bad luck. – molbdnilo Oct 14 '14 at 11:22
  • I am interested to know what is the requirement that is demanding such a design. – elimad Oct 14 '14 at 11:22
  • @elimad It's not a 'requirement', but much rather a convenience thing. The idea behind is, that by destroying the object, the variable pointing to the now invalid memory is nulled automatically in the process. Having an automatism like this in place safeguards you against forgetting to null the pointer. – ATaylor Oct 14 '14 at 11:32
  • Why not use `std::unique_ptr`/`std::shared_ptr`? – avakar Oct 14 '14 at 12:19
  • @avakar It's not quite what I'm looking for, that's all. I was looking for something simple with little overhead. Still thanks a lot for contributing. – ATaylor Oct 14 '14 at 12:28

4 Answers4

1

Casting a Child * to a Base * is safe. Casting a Child** to a Base** is not because those are unrelated. The problem with doing this is that if I cast to void * then to Base**, I can now assign a Base * to a pointer of type Derived *, like this:

#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();
}
Moby Disk
  • 3,761
  • 1
  • 19
  • 38
  • I see the problem, alright. Though let's assume we'd save a `void **` instead of a `Base **`, would that work? That way, this kind of fault is out of the window without another cast. As I said, the entire point of the above code is to NULL the variable when the destructor is called. – ATaylor Oct 14 '14 at 11:38
  • Why did you change the valid `int main()` to the _invalid_ `void main()`? – Lightness Races in Orbit Oct 14 '14 at 12:33
  • Yes, saving a void ** minimizes the likelihood of someone modifying the original pointer. But perhaps std::shared_ptr or std::weak_ptr is a better solution? – Moby Disk Oct 14 '14 at 12:36
0

What would happen if:

  1. Any of the Base functions call some other member func (either of Base or of Child? Virtual or non-virtual.
  2. And what is the guarantee that the void *points to the Child* ? For ex: nothing prevents us to write in main:

    void *pv; new Child(pv);

These should point to say that such type cast is not guaranteed.

If we really need typecasting base<->derived, RTTI is the mechanism. Also see wiki for RTTI:

Community
  • 1
  • 1
elimad
  • 1,132
  • 1
  • 15
  • 27
  • The sole purpose of this variable is to NULL the variable once the object gets destroyed. It is the responsibility of the programmer to not use it for anything else. I am aware of the concept of RTTI, but that's not what I'm looking for here. Besides, it's a pointer to the variable `pv` not, the value of `pv` I'm trying to pass here. – ATaylor Oct 14 '14 at 11:33
  • `nulled automatically` interesting! But your mechanism is not guaranteed to work, as you might have already guessed by now. is `std::shared_ptr` acceptable? – elimad Oct 14 '14 at 11:36
  • Or probably your own `delete` operator? – elimad Oct 14 '14 at 11:37
  • While `shared_ptr` is applicable for a similar cause, it requires quite a bit of overhead, which seems kinda...well, not nice for a single variable. I know for a fact that there's only a single instance of this variable and by checking against NULL, I know it's still valid. My own `delete` operator, hm? This would require me to overload it for every subclass, which is what I don't want. – ATaylor Oct 14 '14 at 11:41
0

You can check, if the the void* pointer from your function can be cast to the Base* with the dynamic_cast<Child*> c++ function and check if the result is equal to 0 (no cast possible).

But a dynamic_cast is not very efficient and the cast ist very slow.

andreashager
  • 667
  • 3
  • 9
  • My apologies, but you seem to have misread the question. I'm not looking for a pointer to `Child`, but to a pointer to a pointer. Meaning, a pointer to the variable holding the reference. – ATaylor Oct 14 '14 at 11:30
0

Converting from Base* to Derived* and vice versa can change the bits in the value -- you cannot safely convert from Base** to Derived**. The same in theory can be true for void*, but in practice is less likely.

Storing a void** is more likely to work than storing a Base** marginally. Still undefined.

Converting back and forth from void* is only valid if you go to and from the exact same type.

The most common case where the address changes when you simply change the type is with multiple inheritance:

struct Base1 { char x; };
struct Base2 { char y; };
struct Derived: Base1, Base2 {};

In this case, Derived* d points at the same address as Base1, but Base2 is after that. A pointer to the Base2 subobject of Derived will have a different binary address.

In C++, there are a limited number of situations where a pointer-to-A can be reinterpreted as a pointer-to-B. These are when the two are layout compatible. Layout compatible only occurs when the types in question are plain old data/standard layout, and when basically one class is a prefix of the other. Most other situation, the compiler is pretty much free to rearrange where things live.

At best you can delve into your particular implementation's object layout, and hope you didn't miss anything. In practice, it is often better to use something like a type-erased destroyer rather than risk the problem: store both the void** and a pointer-to-function that knows what type it was cast from, and casts it back to that.

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;
    }
  };
}

here we have our cleanup_stuff struct that stores a void* and an operation on said void*. Our clear_pointer takes a pointer-to-pointer, and creates a cleanup_stuff that stores the clearing of said pointer at a later point in time.

Store a cleanup_stuff in your Base, and have it take its pointer as a template T**. In the destructor, execute the cleanup_stuff. This has an extra pointer overhead over your solution, and does not execute undefined behavior.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • My apologies, but how can a cast change the value of a pointer? How could a union of pointers ever work then? Isn't a `void *` pointing to 0x12345 and a `Base *` pointing to 0x12345 the same value? – ATaylor Oct 14 '14 at 12:07
  • @ATaylor: Consider multiple inheritance. – Lightness Races in Orbit Oct 14 '14 at 12:12
  • @LightnessRacesinOrbit As I stated before (multiple times), I'm only operating on the pointer variable, not the object itself. Or is a pointer to a multiple inheritance object actually different from any regular object? – ATaylor Oct 14 '14 at 12:25
  • 2
    @ATaylor: Casting from pointer-to-[one of the-]base to pointer-to-derived and back again can change the pointer value when it points to an object whose type is in a multiple inheritance tree, yes. That's because different subobjects may live in different places. `struct Base1 { int x; }; struct Base2 { int y; }; struct Derived : Base1, Base2 { int z; }; int main() { Derived d; assert((void*)&d != (void*)static_cast(&d)); }` ([demo](http://coliru.stacked-crooked.com/a/e0938918c5dd33d0)) – Lightness Races in Orbit Oct 14 '14 at 12:29
  • @LightnessRacesinOrbit That...is interesting. Well, not really applicable here (since MI isn't used), but something to keep in mind for sure. I'll just stick with 'Keep your hands off it and live a happier life'. Thanks for telling me about this. – ATaylor Oct 14 '14 at 12:32
  • @ATayour C++11 solution to your problem added. – Yakk - Adam Nevraumont Oct 14 '14 at 13:20