14

Sorry for the long title but I did want to be specific. I expected the following code to work but it doesn't and I can't figure out why :/

#include <cstdio>
#include <cassert>

class UniquePointer
{
public:
    void Dispose()
    {
        delete this;
    }

    friend void SafeDispose(UniquePointer*& p)
    {
        if (p != NULL)
        {
            p->Dispose();
            p = NULL;
        }
    }
protected:
    UniquePointer() { }
    UniquePointer(const UniquePointer&) { }
    virtual ~UniquePointer() { }
};

class Building : public UniquePointer
{
public:
    Building()
    : mType(0)
    {}
    void SetBuildingType(int type) { mType = type; }
    int GetBuildingType() const { return mType; }
protected:
    virtual ~Building() { }
    int mType;
};

void Foo()
{
    Building* b = new Building();
    b->SetBuildingType(5);
    int a = b->GetBuildingType();
    SafeDispose(b);     // error C2664: 'SafeDispose' : cannot convert parameter 1 from 'Building *' to 'UniquePointer *&'
    b->Dispose();
}

int main(int argc, char* argv[])
{
    Foo();
    return 0;
}
Julien Lebot
  • 3,092
  • 20
  • 32
  • 1
    Note that there is nothing "safe" about what you are trying to do. If `b` is set to `NULL` then `b->Dispose();` causes behaviour that is just as undefined as if `b` pointed to a deleted object. – CB Bailey Aug 25 '11 at 07:15
  • @Charles: Edited, waiting for review. This bug is not a part of a question, is it? – Janusz Lenar Aug 25 '11 at 08:41
  • Also, no need for `SafeDispose` to be a friend since `Dispose` is public. – Janusz Lenar Aug 25 '11 at 08:43
  • The real answer is that the `Building` is a subclass of `UniquePointer` but the `Building*` is a subclass of `UniquePointer*`. That's why you can't cast `Building*` to `UniquePointer*&` - it's not a polymorphism. – Janusz Lenar Aug 25 '11 at 08:49
  • @Charles: Yeah wasn't meant to be safe, it was just me experimenting with the language :) I wanted to know how systems like COM are implemented so I started off with a simple unique pointer, then later on I would have moved to a reference counting system. – Julien Lebot Aug 25 '11 at 09:02

5 Answers5

45

Imagine it were legal. Then you could write code like this:

class Animal : public UniquePointer
{
};

void Transmogrify(UniquePointer*& p)
{
    p = new Animal();
}

void Foo()
{
    Building* b = nullptr;
    Transmogrify(b);
    b->SetBuildingType(0); // crash
}

Observe that you have violated the type system (you put an Animal where a Building should be) without requiring a cast or raising a compiler error.

Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
  • I see your point. The reason I wanted the reference to pointer was to be able to set that pointer to NULL but I obviously missed out all the side effects this could have. – Julien Lebot Aug 25 '11 at 09:08
6

I do not think that it is possible to make it work the way you have it designed. Instead, try the following:

template <typename T>
void SafeDispose(T * & p)
{
    if (p != NULL)
    {
        p->Dispose();
        p = NULL;
    }
}

class UniquePointer
{
public:
    void Dispose()
    {
        delete this;
    }

protected:
    UniquePointer() { }
    UniquePointer(const UniquePointer&) { }
    virtual ~UniquePointer() { }
};
wilx
  • 17,697
  • 6
  • 59
  • 114
  • I like your solution, in fact should you have told why it is not possible to do it the way I did it this would have been my accepted answer. – Julien Lebot Aug 25 '11 at 09:11
3

It is not allowed because if it were you could do the following:

friend void SafeDispose(UniquePointer*& p)
{
    p = new UniquePointer();   
}


Building* building;
SafeDispose(building)
//building points to a UniquePointer not a Building.

I guess the work around would be a template function.

JE42
  • 4,881
  • 6
  • 41
  • 51
1

To answer the title of your question, you cannot bind a non-const reference to base to a derived class instance because you could then set that reference to a pointer to a base instance that isn't a derived. Consider this function:

void Renew(UniquePointer *& p) {
  delete p;
  p = new UniquePointer();
}

if you could pass it a pointer to Building you would be able to set it incorrectly to point to a UniquePointer instance.

As it has already been suggested the solution is to change your reference to a plain pointer. Not only this solves your problem, but it is also a better implementation of SafeDispose(); as you wrote it this function gave the false idea that you would always set to 0 all your UniquePointer instances. But what would happen if somebody wrote (assuming UniquePointer constructor was public for simplicity):

UniquePointer *p1 = new UniquePointer();
UniquePointer *p2 = p1;
SafeDispose(p1);

They would expect all of their UniquePointers to be properly taken care of, when p2 is actually invalid.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • I could have probably given everything a better name than what I did. I intended SafeDispose to set the pointer to NULL and check for NULL, but I didn't consider the fact that the callee could have a copy of the pointer. But it is important to note that there is no sharing whatsoever, I was interested in a "singe ownership" system after reading Google's coding style. – Julien Lebot Aug 25 '11 at 09:19
0

I guess your SafeDispose should probably look more like :

friend void SafeDispose(UniquePointer** p) ...

In order to invoke it using

SafeDispose(&(UniquePointer*)b);

Then it should work this way.

But your next statement

b->Dispose(); 

will break cause b should now be NULL, cause it has been disposed and set to NULL by your SafeDispose method.

Seb T.
  • 1,104
  • 12
  • 27
  • But `&b` is an rvalue of type `Building**` which you can't pass to a function taking a `UniquePointer**` for pretty much identical reasons that you can't bind a `Building*` to a non-const `UniquePointer*&`. – CB Bailey Aug 25 '11 at 07:36
  • Thanks for pointing this out. Adding a cast would probably fix this issue right ?! (updated sample code). Then I guess that wilx answer (by using a template) makes more sense. – Seb T. Aug 25 '11 at 08:15
  • 1
    No, a cast doesn't fix the issue it creates a new one. The cast converts the pointer but the result of the cast is an rvalue and you can't then take the address of an rvalue. – CB Bailey Aug 25 '11 at 08:32