Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Learning C++: returning references AND getting around slicing

I'm having a devil of a time understanding references. Consider the following code:

class Animal
{
public:
    virtual void makeSound() {cout << "rawr" << endl;}
};

class Dog : public Animal
{
public:
    virtual void makeSound() {cout << "bark" << endl;}
};

Animal* pFunc()
{
    return new Dog();
}

Animal& rFunc()
{
    return *(new Dog());
}

Animal vFunc()
{
    return Dog();
}

int main()
{
    Animal* p = pFunc();
    p->makeSound();

    Animal& r1 = rFunc();
    r1.makeSound();

    Animal r2 = rFunc();
    r2.makeSound();

    Animal v = vFunc();
    v.makeSound();
}

And the results are: "bark bark rawr rawr".

In a Java way of thinking, (which has apparently corrupted my conceptualization of C++), the results would be "bark bark bark bark". I understand from my previous question that this difference is due to slicing and I now have a good understanding of what slicing is.

But let's say that I want a function that returns an Animal value that is really a Dog.

  1. Do I understand correctly that the closest that I can get is a reference?
  2. Furthermore, is it incumbent upon the one using the rFunc interface to see that the reference returned is assign an Animal&? (Or otherwise intentionally assign the reference to an Animal which, via slicing, discards polymorphism.)
  3. How on earth am I supposed to return a reference to a newly generated object without doing the stupid thing I did above in rFunc? (At least I've heard this is stupid.)

Update: since everyone seems to agree so far that rFunc it illegitimate, that brings up another related questions:

If I pass back a pointer how do I communicate to the programmer that the pointer is not theirs to delete if this is the case? Or alternatively how do I communicate that the pointer is subject to deletion at any time (from the same thread but a different function) so that the calling function should not store it, if this is the case. Is the only way to communicate this through comments? That seems sloppy.

Note: All this is leading up to an idea for a templated shared_pimpl concept I was working on. Hopefully I'll learn enough to post something about that in a couple of days.

like image 970
JnBrymn Avatar asked Dec 10 '10 04:12

JnBrymn


2 Answers

1) If you're creating new objects, you never want to return a reference (see your own comment on #3.) You can return a pointer (possibly wrapped by std::shared_ptr or std::auto_ptr). (You could also return by copy, but this is incompatible with using the new operator; it's also slightly incompatible with polymorphism.)

2) rFunc is just wrong. Don't do that. If you used new to create the object, then return it through an (optionally wrapped) pointer.

3) You're not supposed to. That is what pointers are for.


EDIT (responding to your update:) It's hard to picture the scenario you're describing. Would it be more accurate to say that the returned pointer may be invalid once the caller makes a call to some other (specific) method?

I'd advise against using such a model, but if you absolutely must do this, and you must enforce this in your API, then you probably need to add a level of indirection, or even two. Example: Wrap the real object in a reference-counted object which contains the real pointer. The reference-counted object's pointer is set to null when the real object is deleted. This is ugly. (There may be better ways to do it, but they may still be ugly.)

like image 74
Dan Breslau Avatar answered Oct 04 '22 21:10

Dan Breslau


To answer the second part of your question ("how do I communicate that the pointer is subject to deletion at any time") -

This is a dangerous practice, and has subtle details you will need to consider. It is racy in nature.

If the pointer can be deleted at any point in time, it is never safe to use it from another context, because even if you check "are you still valid?" every time, it may be deleted just a tiny bit after the check, but before you get to use it.

A safe way to do these things is the "weak pointer" concept - have the object be stored as a shared pointer (one level of indirection, can be released at any time), and have the returned value be a weak pointer - something that you must query before you can use, and must release after you've used it. This way as long the object is still valid, you can use it.

Pseudo code (based on invented weak and shared pointers, I'm not using Boost...) -

weak< Animal > animalWeak = getAnimalThatMayDisappear();
// ...
{
    shared< Animal > animal = animalWeak.getShared();
    if ( animal )
    {
        // 'animal' is still valid, use it.
        // ...
    }
    else
    {
        // 'animal' is not valid, can't use it. It points to NULL.
        // Now what?
    }
}
// And at this point the shared pointer of 'animal' is implicitly released.

But this is complex and error prone, and would likely make your life harder. I'd recommend going for simpler designs if possible.

like image 37
Hexagon Avatar answered Oct 04 '22 19:10

Hexagon