Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unique_ptr and polymorphism

I have some code that currently uses raw pointers, and I want to change to smart pointers. This helps cleanup the code in various ways. Anyway, I have factory methods that return objects and its the caller's responsibility to manager them. Ownership isn't shared and so I figure unique_ptr would be suitable. The objects I return generally all derive from a single base class, Object.

For example,

class Object { ... };
class Number : public Object { ... };
class String : public Object { ... };

std::unique_ptr<Number> State::NewNumber(double value)
    {
        return std::unique_ptr<Number>(new Number(this, value));
    }

std::unique_ptr<String> State::NewString(const char* value)
    {
        return std::unique_ptr<String>(new String(this, value));
    }

The objects returned quite often need to be passed to another function, which operates on objects of type Object (the base class). Without any smart pointers the code is like this.

void Push(const Object* object) { ... } // push simply pushes the value contained by object onto a stack, which makes a copy of the value
Number* number = NewNumber(5);
Push(number);

When converting this code to use unique_ptrs I've run into issues with polymorphism. Initially I decided to simply change the definition of Push to use unique_ptrs too, but this generates compile errors when trying to use derived types. I could allocate objects as the base type, like

std::unique_ptr<Object> number = NewNumber(5);

and pass those to Push - which of course works. However I often need to call methods on the derived type. In the end I decided to make Push operate on a pointer to the object stored by the unique_ptr.

void Push(const Object* object) { ... }
std::unique_ptr<Object> number = NewNumber(5);
Push(number.get());

Now, to the reason for posting. I'm wanting to know if this is the normal way to solve the problem I had? Is it better to have Push operate on the unique_ptr vs the object itself? If so how does one solve the polymorphism issues? I would assume that simply casting the ptrs wouldn't work. Is it common to need to get the underlying pointer from a smart pointer?

Thanks, sorry if the question isn't clear (just let me know).

edit: I think my Push function was a bit ambiguous. It makes a copy of the underlying value and doesn't actually modify, nor store, the input object.

like image 757
user1520427 Avatar asked Nov 08 '12 11:11

user1520427


People also ask

What is a unique_ptr?

(since C++11) std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

What does unique_ptr get do?

unique_ptr::getReturns a pointer to the managed object or nullptr if no object is owned.

Does unique_ptr call destructor?

Yes. Well the unique ptr has a function object that by default invokes delete on the pointed to object, which calls the destructor. You can change the type of that default deleter to do almost anything.

Can unique_ptr be copied?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.


1 Answers

Initially I decided to simply change the definition of Push to use unique_ptrs too, but this generates compile errors when trying to use derived types.

You likely did not correctly deal with uniqueness.

void push(std::unique_ptr<int>);
int main() {
    std::unique_ptr<int> i;
    push(i); // Illegal: tries to copy i.
}

If this compiled, it would trivially break the invariant of unique_ptr, that only one unique_ptr owns an object, because both i and the local argument in push would own that int, so it is illegal. unique_ptr is move only, it's not copyable. It has nothing to do with derived to base conversion, which unique_ptr handles completely correctly.

If push owns the object, then use std::move to move it there. If it doesn't, then use a raw pointer or reference, because that's what you use for a non-owning alias.

like image 105
Puppy Avatar answered Sep 19 '22 14:09

Puppy