Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the preferred C++ idiom to own a collection of polymorphic objects?

Consider the following classes

class Base {
public:
    virtual void do_stuff() = 0;
};

class Derived : public Base {
public
    virtual void do_stuff() { std::cout << "I'm useful"; };
};

Now let's say I want to have another class responsible for owning objects of Base's derived types and iterate through them calling their do_stuff() method. It looks like this, but I don't know what T should be declared as

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(T item) {
        items.push_back(item);
    }

    vector<T> items;
}

I see a few possibilities:

T can't be Base, since I would only be able to add objects of concrete type Base, so that's out of the question.

T can be Base* or Base&, but now I need to trust the caller of add_item() to pass me a pointer or a reference to an object that will still exist when I retrieve it from items. I can't delete the elements in Owner's destructor, since I don't know that they were dynamically allocated. However, they should be delete'd if they were, which leaves me with ambiguous ownership.

T can be Base* or Base& and I add a Base* create_item<DerivedT>() { return new DerivedT; } method to Owner. This way, I know the pointer will remain valid and I own it, but I'm unable to call a non-default constructor on DerivedT. Also, Owner becomes responsible for instantiating objects as well. I also have to delete every item in Owner's destructor, although that's not much of an issue.

Basically, I'd like to be able to do something similar to:

Owner owner;

void add_one() {
    Derived d;

    owner.add_item(d);
}

void ready() {
    owner.do_all_stuff();
}

void main() {
    for(int i = 0; i < 10; ++i) {
        add_one();
    }
    ready();
}

I'm sure there's something related to move semantics in there (I could move the objects passed to add_items() to own them) but I still can't figure out how my collection would be declared.

What is the C++ idiom for this sort of polymorphic ownership (particularly with STL containers)?

like image 428
anthonyvd Avatar asked Jul 08 '13 16:07

anthonyvd


People also ask

What is polymorphism in C language?

The word polymorphism means having many forms. Typically, polymorphism occurs when there is a hierarchy of classes and they are related by inheritance. C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object that invokes the function.

What is polymorphism in C++ with example?

Polymorphism in C++ means, the same entity (function or object) behaves differently in different scenarios. Consider this example: The “ +” operator in c++ can perform two specific functions at two different scenarios i.e when the “+” operator is used in numbers, it performs addition.

Why do we use polymorphism in C++?

Polymorphism in C++ allows us to reuse code by creating one function that's usable for multiple uses. We can also make operators polymorphic and use them to add not only numbers but also combine strings. This saves time and allows for a more streamlined program.

Why do we use polymorphism?

Polymorphism is the ability of an object to take on many forms. The most common use of polymorphism in OOP occurs when a parent class reference is used to refer to a child class object. Any Java object that can pass more than one IS-A test is considered to be polymorphic.


2 Answers

Polymorphic objects have to be handled by pointer or reference. Since their lifetime is probably not bound to a particular scope they will also probably have dynamic storage duration, which means you should use a smart pointer.

Smart pointers such as std::shared_ptr and std::unique_ptr work just fine in the standard collection types.

std::vector<std::unique_ptr<Base>>

Using this in Owner looks like:

class Owner {
public:
    void do_all_stuff() {
        //iterate through all items and call do_stuff() on them
    }

    void add_item(std::unique_ptr<Base> item) {
        items.push_back(std::move(item));
    }

    vector<std::unique_ptr<Base>> items;
}

The argument type to add_item identifies the ownership policy required for adding an item, and requires the user to go out of their way to screw it up. For example they can't accidentally pass a raw pointer with some implicit, incompatible ownership semantics because unique_ptr has an explicit constructor.

unique_ptr will also take care of deleting the objects owned by Owner. Although you do need to ensure that Base has a virtual destructor. With your current definition you will get undefined behavior. Polymorphic objects should pretty much always have a virtual destructor.

like image 144
bames53 Avatar answered Sep 21 '22 06:09

bames53


Assuming from your context that Owner is the sole owner of the contained objects,T should be unique_ptr<Base> (where unique_ptr comes from boost or std depending on your C++11 availability). This properly recognizes that it's solely owned by the container and additionally shows the ownership transferral semantics in your add_item call.

like image 45
Mark B Avatar answered Sep 22 '22 06:09

Mark B