11
\$\begingroup\$

I have a concept based polymorphism example listed below. I allow the user to provide any type that implements the draw method and then I add it into a vector of unique_ptr to concept base. If I have a pointer or reference I want to be able to deal with that even though the solution has value semantics in mind. I thus created a wrapper type which forwards to the correct implementation. I am hoping to get some feedback on if this is the best way to deal with the issue at hand.

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

class drawable_concept{
public:
    drawable_concept() = default;
    virtual ~drawable_concept() = default;
    drawable_concept(drawable_concept const&) = delete;
    drawable_concept& operator = (drawable_concept const& ) = delete;
    virtual void draw() = 0;
};

template<class T>
class drawable_model : public drawable_concept{
public:
    typedef T model_type;
    drawable_model(T const& model) : model_(model){}
    void draw(){
        model_.draw();
    }
    ~drawable_model() = default;
private:
    T model_;
};

template<class T>
class drawable_forwarder{
public:

    drawable_forwarder(T const& item) : item_(item){}

    inline void draw(){
        item_->draw();
    }
private:
    T item_;
};

class graphics_surface{
public:
    void render(){
        std::for_each(std::begin(controls_), std::end(controls_), [] (std::unique_ptr<drawable_concept> const& control){
            control->draw();
        });
    }

    template<class T>
    void push_back(T control){
        auto t = new drawable_model<T>(std::move(control));
        controls_.push_back(std::unique_ptr<drawable_concept>(t));
    }
private:
    std::vector<std::unique_ptr<drawable_concept>> controls_;
};

struct triangle{
    void draw(){
        std::cout << "Triangle" << std::endl;
    }
};

struct square{
    void draw(){
        std::cout << "Square" << std::endl;
    }
};

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

    graphics_surface surface;
    surface.push_back(triangle());
    surface.push_back(square());

    std::shared_ptr<triangle> ptr(new triangle);
    drawable_forwarder<std::shared_ptr<triangle>> fwd(ptr);
    surface.push_back(fwd);

    surface.render();

    return 0;
}
\$\endgroup\$
  • 3
    \$\begingroup\$ Be careful with the wording. Your drawable_concept is an interface. Concepts relate to a template mechanism that might come in C++14. \$\endgroup\$ – Nobody Feb 17 '14 at 13:37
  • \$\begingroup\$ Just to complete the previous comment I am talking about concepts light \$\endgroup\$ – Nobody Feb 17 '14 at 13:46
  • 1
    \$\begingroup\$ @Nobody Sean Parent gave a talk at Boost Con entitle "Value Semantics and Concepts Based Polymorphism" which uses exactly this pattern. A similar pattern is used by boost::any, std::function and std::shared_ptr to achieve type erasure. But this extends the idea further by allowing operations on the stored object and retains value semantics on the objects being managed (i.e. you don't have to use traditional inheritance based OO) You can see the talk here: youtube.com/watch?v=_BpMYeUFXv8. It's very interesting. \$\endgroup\$ – Peter R Feb 17 '14 at 15:41
  • \$\begingroup\$ It seems this has been cross posted: stackoverflow.com/questions/21821948/concept-based-polymorphism \$\endgroup\$ – Nobody Mar 17 '14 at 10:34
  • \$\begingroup\$ It's a bit late, but thank you very much. I didn't know about concept-based polymorphism until there, and I already found a project of mine where it solved some problems quite elegantly :) \$\endgroup\$ – Morwenn Mar 23 '14 at 18:15
4
\$\begingroup\$

There is not much to be said actually, the concept-based polymorphism seems to be well implemented. You can slightly improve your push_back method by having it emplace_back the std::unique_ptr. It will be a little bit less verbose:

template<class T>
void push_back(T control){
    auto t = new drawable_model<T>(std::move(control));
    controls_.emplace_back(t);
}

Also, I suppose that a draw shouldn't alter the object being drawn. Therefore, you better const-qualify all of your draw methods.

You could also be more consistent with the way you use inline: you inlined drawable_forwarder's draw method but not drawable_model while it basically does the same thing.


One fun addition would be to write a method that would create the drawable_model object in-place. That would require to add the following constructor to drawable_model:

template<typename... Args>
drawable_model(Args&&... args):
    model_(std::forward<Args>(args)...)
{}

And the following method to graphics_surface (not sure about the name):

template<typename T, typename... Args>
void emplace_back(Args&&... args)
{
    auto t = new drawable_model<T>(std::forward<Args>(args)...);
    controls_.emplace_back(t);
}

Then, you could use it this way:

int main()
{
    graphics_surface surface;

    // Assume triangle and rectangle have complete constructors
    surface.emplace_back<triangle>(3.5, 6.8, 4.8, 3.2);
    surface.emplace_back<square>(/* whatever */);

    surface.render();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.