3

I have been experimenting with abstract types. The code below gives me a desired effect.

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

class derived: public base{
public:
 void do_stuff(){/*stuff*/}
};

class manager{
 vector<shared_ptr<base>> ptrs;
public:
 void add(base* ptr){
  ptrs.emplace_back(ptr);
 }
};

manager foo;
foo.add(new derived());

Fine and dandy, but it's awkward because the user is not only dealing with pointers, but has to use new without ever calling delete. My question is if there's a way I can implement this where the user of manager doesn't ever have to deal with pointers or new.

foo.add(derived()); //example

My attempts to implement this end up as:

class manager{
 vector<shared_ptr<base>> ptrs;
public:
 void add(base& ref){
  ptrs.emplace_back(&ref);
 }
};

But, the compiler says no known conversion from 'derived' to 'base&'. I have no idea how to make a reference to base compatible with a reference to derived. How do I get around this?

Willy Goat
  • 1,175
  • 2
  • 9
  • 24

4 Answers4

4

Pass unique_ptr

Your add function takes ownership of this object. A safe way of passing ownership is to pass unique_ptr.

Using a unique_ptr is fairly flexible because you can construct a shared_ptr from a unique_ptr or if you change your mind in the future you can store the unique_ptr directly.

class manager{
  vector<shared_ptr<base>> ptrs;
public:
  void add(std::unique_ptr<base> ptr){
    ptrs.emplace_back(std::move(ptr));
  }
};

manager foo;
foo.add(std::make_unique<derived>());

Using a temporary std::unique_ptr you avoid the owning raw pointer that is not exception safe. By using make_unique you can avoid writing new.

Live demo.

Pass a Factory

Another option if the caller really doesn't want to have to deal with any kind of pointer is to pass some sort of Factory that the add function uses to construct the object. The Factory could simply be a static create function on the derived class itself:

using Factory = std::function<std::unique_ptr<base>()>;

class manager{
 std::vector<std::shared_ptr<base>> ptrs;
public:
 void addUsing(const Factory& factory){
  ptrs.emplace_back(factory());
 }
};

class derived : public base {
public:
 ...
  static std::unique_ptr<derived> create() { 
    return std::make_unique<derived>();
  }
};

manager foo;
foo.addUsing(derived::create);

Live demo.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • Very interesting. I'll probably be using this in my project. Not as elegant as I'd hoped, but It's not the end of the world if the user has to know there's pointers. – Willy Goat Oct 01 '15 at 00:00
  • @WillyGoat I've added another option where the caller doesn't really need to know there are any pointers involved. – Chris Drew Oct 01 '15 at 00:18
1

You can let your add() function be passed the arguments to be used in the construction of type T, where T is specified as the type of a subclass.

template <typename T, typename... TArgs>
void add(TArgs&&... args)
{
    ptrs.emplace_back(std::make_shared<T>(std::forward<TArgs>(args)...));
}

Which can then be called as follows:

bm.add<derived_a>( "hello" ); // derived_a constructor takes a string
bm.add<derived_b>( 42 );      // derived_b constructor takes an int

Full example

#include <string>
#include <vector>
#include <memory>

class base
{
public:
    virtual void f() = 0;
};

class derived_a : public base
{
public:
    derived_a( std::string const& s ) : s_{ s } {}
    void f() override { std::cout << "derived_a::string = " << s_ << '\n'; }

private:
    std::string s_;
};

class derived_b : public base
{
public:
    derived_b( int i ) : i_{ i } {}
    void f() override { std::cout << "derived_b::int = " << i_ << '\n'; }

private:
    int i_;
};

class base_manager
{
public:
    template <typename T, typename... TArgs>
    void add( TArgs&&... args )
    {
        ptrs.emplace_back( std::make_shared<T>( std::forward<TArgs>( args )... ) );
    }

    void print() { for ( auto& d : ptrs ) d->f(); }

private:
    std::vector<std::shared_ptr<base>> ptrs;
};

int main()
{
    base_manager bm;
    bm.add<derived_a>( "hello" );
    bm.add<derived_b>( 42 );
    bm.print();
}
bku_drytt
  • 3,169
  • 17
  • 19
  • This is great but it relies on the type being added being known at compile time. Often with polymorphic types the type will not be known till runtime. – Chris Drew Sep 30 '15 at 23:42
  • @ChrisDrew I understand, but I based it off of his example where he called `manager.add(derived()`, which implied (from my point of view) that he knew the type he was adding. – bku_drytt Sep 30 '15 at 23:48
  • Wow, I really like the result of this. Unfortunately there's a lot of unfamiliar code there, so I'll have to do some reading before I can use something like that. – Willy Goat Sep 30 '15 at 23:56
  • @WillyGoat In that case, I recommend you head on over to: http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – bku_drytt Sep 30 '15 at 23:59
1

You can't pass a temporary (an r-value) to a non-const reference. Also you try to take the address of that temporary object, which will in the end produce a dangling pointer and undefined behavior.

Assuming you want to pass an object of unknown runtime type to the manager:
One thing you can do is using some sort of polymorphic copy mechanism (like a virtual clone method) and make an internal copy of the object on the heap (it has to be polymorphic, to avoid object slicing).

class base {
public:
    virtual void do_stuff() = 0;
    virtual shared_ptr<base> clone() const = 0;
    virtual ~base()=default;
};

class derived : public base {
    int data;
public:
    derived() :data(0) {};
    derived(const derived& other) :data(other.data)
    {};
    virtual shared_ptr<base> clone() const override { 
        return make_shared<derived>(*this); 
    };
    void do_stuff() {/*stuff*/ }
};

class manager {
    vector<shared_ptr<base>> ptrs;
public:
    void add(const base& obj) {
        ptrs.emplace_back(obj.clone());
    }
};
int main() {
    manager foo;
    foo.add(derived());
}

without the clone, it would look something like this:

void add(const base& obj) {
    if (typeid(obj)== typeid(derived) ){
        ptrs.emplace_back(make_shared<derived>(static_cast<const derived&>(obj)));              
    }
    else if (typeid(obj) == typeid(derived2)) { 
    ...         
}
MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • That's very nice! I take it this couldn't be done with a copy constructor, right? I'm guessing it'd run into the same problems when you try to `emplace_back(&base(obj))`? – Willy Goat Oct 01 '15 at 00:07
  • @Willy Goat. You can do it with a copy constructor, but then you need a if-else statement inside the add method, that creates a copy of the right type. This has also the disadvantage, that everytime, you add a new class to your project, you also have to extend that if-elseif-.. statement – MikeMB Oct 01 '15 at 00:10
  • @WillyGoat For reference this technique is closely related to the [Prototype pattern](https://en.wikipedia.org/wiki/Prototype_pattern). – Chris Drew Oct 01 '15 at 00:22
  • Aah. Thank you. I think a clone method is my preferred solution for now. – Willy Goat Oct 01 '15 at 00:24
  • 1
    @WillyGoat: there is actually a very nice extension to this pattern, that doesn't require a clone method, that is explained in this talk:https://www.youtube.com/watch?v=bIhUE5uUFOA. It has the advantage, that it is non-intrusive, but the internals of manager become quite complicated. Maybe I'll update my answer later. – MikeMB Oct 01 '15 at 00:27
  • @MikeMB Oh, nice, I'll definitely listen to the talk. – Willy Goat Oct 01 '15 at 00:36
0

Your original question seems to be concerned over the fact that the user/caller creates a pointer and hands it off and never deletes it. My example below, simply makes it explicit to the user that he can hand it off and forget about it. In otherwords, require the user to pass a shared_ptr...

#include <stdlib.h>
#include <vector>
#include <memory>

using namespace std;

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

class derived : public base{
public:
    void do_stuff(){/*stuff*/ }
};

class manager{
    vector<shared_ptr<base>> ptrs;
public:
    void add(shared_ptr<base> ptr){
        ptrs.emplace_back(ptr);
    }
};

int main()
{
    manager foo;
    shared_ptr<derived> bp(new derived()); //require the user supply a smart pointer
    foo.add(bp);
    return 0;
}

This is simpler than the other posts, and may not be as forward thinking, but it does not require the derived class to implement additional base members. In many cases, it is may be enough.

Les
  • 10,335
  • 4
  • 40
  • 60
  • How is this simpler than passing a `unique_ptr`? If you pass a `shared_ptr` you have the overhead of incrementing and decrementing the reference count on the `shared_ptr`. Also it is not clear if `add` is taking ownership or not. – Chris Drew Oct 01 '15 at 01:37