6

I was trying to answer the question mentioned here by passing the reference to the pointer instead of pointer to pointer like this:

class Parent 
{
};

class Child : public Parent 
{
};

void RemoveObj(Parent*& pObj)
{
    delete pObj;
    pObj = NULL;
}

int main()
{
    Parent* pPObj = new Parent;
    Child*  pCObj = new Child;
    pPObj = new Parent();
    pCObj = new Child();



    RemoveObj(pPObj);
    RemoveObj(pCObj); // This is line 32
    return 1;
}

But this produces the following compiler error at line 32:

error C2664: 'RemoveObj' : cannot convert parameter 1 from 'Child *' to 'Parent *&'

I agree that conversion from Child** to Parent** is not allowed. But why this conversion is also not allowed?

Community
  • 1
  • 1
Naveen
  • 74,600
  • 47
  • 176
  • 233
  • Why return 1; at the end of main() ? Isn't it supposed to be return 0; ? – DeadHead Apr 10 '09 at 09:06
  • I just copy pasted the code from linked question..but that is not related to the question – Naveen Apr 10 '09 at 09:08
  • Oh, I know it wasn't really related to the question. Mere curiosity combined with thinking there might have been some gap in my knowledge. – DeadHead Apr 10 '09 at 09:10
  • you can return what you like from main - it comes up as the result value of the program. typically on *nix you might use the value to check if the program worked or not, and it depends on the app as to what the value may mean. – Greg Domjan Apr 10 '09 at 13:45

5 Answers5

7

An object of type Child* cannot be bound to a Parent*& for exactly the same reason that a Child** cannot be converted to a Parent**. Allowing it would allow the programmer (intentionally or not) to break type safety without a cast.

class Animal {};

class DangerousShark : public Animal {};

class CuteKitten : public Animal {};

void f(Animal*& animalPtrRef, Animal* anotherAnimalPtr)
{
    animalPtrRef = anotherAnimalPtr;
}

void g()
{
    DangerousShark myPet;
    CuteKitten* harmlessPetPtr;

    f(harmlessPetPtr, &myPet); // Fortunately, an illegal function call.
}

Edit

I think that some of the confusion arises because of the loose use of the words 'convert' and 'conversion'.

References can't be rebound, unlike objects which can be reassigned, so in the context of references when we speak of conversion we can only be concerned about initializing a new reference.

References are always bound to an object, and from the OP's question it was clear that he is aiming to get a reference that is a direct bind to an existing object. This is only allowed if the object used to initialize the reference is reference-compatible with the type of the reference. Essentially, this is only if the types are the same, or the type of the object is derived from the type of the reference and the reference type is at least as cv-qualified as the initializing object. In particular, pointers to different types are not reference-compatible, regardless of the relationship of the pointed-to types.

In other cases, a reference can be initialized with something that can be converted to the reference type. In these cases, though, the reference must be const and not volatile and the conversion will create a temporary and the reference will be bound to this temporary and not the original object. As pointed out, this is not suitable for the requirements of OP's motivating example.

In summary, a Child can be bound directly to a Parent& but a Child* cannot be directly bound to a Parent*&. A Parent* const& can be initialized with a Child*, but the reference will actually bind to a temporary Parent* object copy-initialized from the Child* object.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • @Charles Bailey: Quite a few issues: [1] The correct dtor will not be called for CuteKitten objects since the dtor for Animal is not virtual. [2] harmlessPtr is undefined. – dirkgently Apr 10 '09 at 09:34
  • Thanks, I changed harmless to harmlessPet at the last moment but only in one place. There's no delete going on in my example so I'm not sure how your point[1] applies. – CB Bailey Apr 10 '09 at 09:45
  • I disagree with your answer. It's only a mater of temporary and constness. Use `void f(Animal* const & animalPtrRef, Animal* anotherAnimalPtr)` and the call is then legal: tada ! you formed a const ref to Animal* from a CuteKitten* – Benoît Apr 10 '09 at 09:51
  • The const reference in your function is not bound to the caller's pointer-to-derived precisely because you had to make the temporary. My answer is an example why the direct binding - which can happen for const reference parameters as well - cannot be allowed. – CB Bailey Apr 10 '09 at 10:01
  • @Benôit: I agree with the answer. The reason the const& version works is that the compiler is allowed to bind a const& to a temporary element. The call will create a temporary Animal* from CuteKitten* (allowed) and then bind the constant reference to the temporary. +1 – David Rodríguez - dribeas Apr 10 '09 at 13:49
  • @dirkgently: virtual destructors are only required if you intend on deleting through a base pointer. In the example the only alive element is myPet and it will be released as a DangerousShark. Virtual destructor is unneeded here (even if recommended for all non-trivial code samples) – David Rodríguez - dribeas Apr 10 '09 at 13:52
4
  • Your classes don't have a virtual function. See FAQ 20.7

  • Beacuse Parent *& is a reference to a pointer to a Parent object. You are passing a pointer to a Child -- these are incompatible types. You can bind a temporary to a const-reference i.e. if you change your parameter to:

    void RemoveObj(Parent* const& foo);

But then you won't be able to do much with this.

It was just a test code so I didn't make any virtual destructors. If I understand correctly in the second call of RemoveObj(), I get a temporary Parent* object which can be passed as a const reference to the function. Is this correct?

I strongly suggest you run the following program in standard C++98 mode, once as in and then again after you have commented out foo(b) and uncommented delete b. Next, try putting in a virtual before ~s(). The differences should be self-explanatory!

#include <iostream>
using namespace std;
struct s { 
  s() {cout << __func__ << endl; }
  ~s() {cout << __func__ << endl; } 
};

struct t : s { 
   t() {cout << __func__ << endl; }
   ~t() {cout << __func__ << endl; } 
};

void foo(s* const& x) { delete x; }

int main() {
 t* b = new t;
 foo(b);
 //delete b;
}
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • It was just a test code so I didn't make any virtual destructors. If I understand correctly in the second call of RemoveObj(), I get a temporary Parent* object which can be passed as a const reference to the function. Is this correct? – Naveen Apr 10 '09 at 09:19
1

You can convert a Child* to a Parent*: this creates a temporary. But you cannot bind a non-const reference to that temporary.

That's not a problem of **/*&/etc. What you are trying to do is totally OK and makes sense. That Shark vs. Kitten has the same issue: not a matter of mixing kitten and sharks. You cannot bind a non-const reference to that unnamed pointer.

This is not the Parent** vs. Child** problem: there, if a Child** was a Parent**, then one could assign p[0] = new NotAChild;. A collection of objects which are all subtypes of A is not a collection of A.

Benoît
  • 3,355
  • 2
  • 29
  • 34
0

type*& is another syntactic form of type** and Parent*& and Child*& are unrelated to each other as well as Parent** and Child** - these are different types not in one class hierarchy.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
0

This won't work for the reasons mentioned by Dirk. If you really need a RemoveObj method then I would just save your newly allocated Child object as a Parent*:

#include <iostream>

class Parent 
{
public:
    virtual ~Parent()
    {
        std::cout << "Parent destructor" << std::endl;
    }
};

class Child : public Parent 
{
public:
    virtual ~Child() 
    {
        std::cout << "Child destructor" << std::endl;
    }
};

void RemoveObj(Parent*& pObj)
{
    delete pObj;
    pObj = NULL;
}



int main (int argc, const char * argv[]) {

    Parent* pPObj = new Parent;
    Parent*  pCObj = new Child;

    RemoveObj(pPObj);    
    RemoveObj(pCObj); // This is line 32


    return 0;
}
Nick Haddad
  • 8,767
  • 3
  • 34
  • 38