1

Background

I am just trying to explore a bit of the available patterns and clever usage possibilities (in c++). There is a lot of information how to use multiple virtual inheritance or the pimpl idiom and there are nice topics about bridges or nested generalization but i was not able to find a good topic for this.

I am aware of (somehow) similar questions like:

Pimpl idiom with inheritance

but i think this is not a duplicate and worth it's own answer. My approach could be absolutely stupid, so please hint me into some helpful direction. This is not a homework and so there is not really any restriction on answers/languages/paradigms, but to not have a discussion about personal preference, please try to reason your answers why a specific pattern/solution is valid, either because other presented solutions are wrong/error prone/... .

Question

What would be a good way to implement the following structure (maybe composition instead of inheritance?). It is possible that different answers could be valid if reuse of code, easier to read/write, easier to extend is the main focus (maybe you can think of other problems?)

The main problem i see in the current implementation is that this is not extensable at all as we get one class for every combination of values for each property we want to model. For the sake of simplicity of the example i use a Camera with the properties Projection (either Ortographic or Perspective) and Behaviour (FirstPerson or FreeLook) but the idea could be the same for any entity A with properties P1,P2,P3,P4 which each can be one out of many subtypes P1=>P1.1,P1.2,... P2=>P2.1,P2.2,...

Projection shall define the implementation of

virtual const glm::mat4& getProjectionMatrix() = 0;

while Behaviour shall define the implementation of

virtual const glm::vec3& getForwardVector() = 0;

Possible Solution 1: Combine pimpl idiom with multiple virtual inheritance

Sidenote: I use the name _this for the pimpl pointer as _this->variable leads to very readable code from my point of view. Of course there is the danger of confusing this and _this in case there exists members with the same name (unlikely but indeed possible).

Camera.h

class Camera{
protected:
    /*Pimpl Idiom extended by multiple virtual inheritance for the AbstractImpl subclasses*/
    class AbstractImpl;
    std::unique_ptr<AbstractImpl> _this;
    /*Private subclasses for AbstractImpl*/
    class Ortographic;
    class Perspective;
    class FirstPerson;
    class FreeLook;
    class OrtographicFirstPerson;
    class OrtographicFreeLook;
    class PerspectiveFirstPerson;
    class PerspectiveFreeLook;        
};

AbstractImpl.h

class Camera::AbstractImpl
{
public:
     /*Also contains all private data members of class Camera (pimpl idiom)*/
     virtual const glm::vec3& getForwardVector() = 0;
     virtual const glm::mat4& getProjectionMatrix() = 0;       
};
class Camera::Ortographic:virtual Camera::AbstractImpl{public:virtual const glm::mat4& getProjectionMatrix() override;};
class Camera::Perspective:virtual Camera::AbstractImpl{public:virtual const glm::mat4& getProjectionMatrix() override;};
class Camera::FirstPerson:virtual Camera::AbstractImpl{public:virtual const glm::vec3& getForwardVector() override;};
class Camera::FreeLook:virtual Camera::AbstractImpl{public:virtual const glm::vec3& getForwardVector() override;};

class Camera::OrtographicFirstPerson:virtual Camera::Ortographic,virtual Camera::FirstPerson{};
class Camera::OrtographicFreeLook:virtual Camera::Ortographic, virtual Camera::FreeLook{};
class Camera::PerspectiveFirstPerson:virtual Camera::Perspective, virtual Camera::FirstPerson{};
class Camera::PerspectiveFreeLook:virtual Camera::Perspective, virtual Camera::FreeLook{};

This obviously is not very useable in case of many properties or values per property but at first glance it looks like the code is reused very well as the both virtual methods are implemented exactly once per necessity. Also i think the code for the part of the subclasses of AbstractImpl is very nicely readable as it feels like assigning attributes. "I want this class to have this properties". Also all subclasses have full access to the datamembers and functions of AbstractImpl which might be necessary (let's say the return value of the overriden virtual method depends on values of the private data members).

Also this looks like an implementation which could be supported very well by code generators as you simply need correct inheritance for a new subclass.

Possible Solution 2:Composition

Another solution i could think of is just to use composition.

Camera.h

class Camera{
protected:
    ProjectionType _projectionType;
    CameraBehaviour _cameraBehaviour;
private:
    class Impl;
    std::unique_ptr<Impl> _this;
};

If the ProjectionType or CameraBehaviour sublcasses depend any values of Camera::Impl an instance would be necessary to pass and i think this would clutter the code a lot. On the pro side we end up with way less classes as we need one class per projectionType and one class per cameraBehaviour. This might be the solution to go if we have many possible values for type and behaviour.

Possible Solution 3: Inheritance

Of course we could just use subclasses of Camera. In this case the pimpl might or not might be used at all as all data members which require protected visibilty shouldn't be part of the pimpl anyway.

Camera.h

class AbstractCamera{
protected:
    /*All protected data members*/
private:
    /*Maybe pimpl idiom makes no sense here because most of the variables might be protected*/
    class Impl;
    std::unique_ptr<Impl> _this;
};

PerspectiveFreeLookCamera.h

class PerspectiveFreeLookCamera: virtual AbstractCamera{
/*Override both methods*/
};

PerspectiveFirstPersonCamera.h

class PerspectiveFirstPersonCamera: virtual AbstractCamera{
/*Override both methods*/
};

And the same for the Ortographic classes. Here the virtual methods would be implemented multiple times as all Perspective/Ortographic... classes share the same implementation for the method

virtual const glm::mat4& getProjectionMatrix() = 0;       

While all FreeLook/FirstPerson classes share the same implementation for

virtual const glm::vec3& getForwardVector() = 0;

Thank you for taking your time, i hope this can be considered a good question as i have put quite some thought into it, such that it is interesting for hopefully many people :)

Edit: If someone can imagine a better title for this question, please feel free to edit it, i had quite a hard time finding a good title.

Possible Solution 4: Templates (Added 4.11 19:00 GMT+2)

Based on the answer of Yakk I have worked on a solution with templates.

Camera.h

class Camera
{
public:     
    /*Forward declaration of public classes of camera
    of course it would be possible to use their own header files to have a stricter one class
    per file nature but i do not really see the need for this*/
    template<typename t_projection, typename t_behaviour>
    class Impl;
    class AbstractImpl; 
    Camera(AbstractImpl *impl);
    ~Camera();
    /* All other public functions for camera*/ 

    /* Example method we want to implement based on tags/traits */
    const glm::mat4& getProjectionMatrix();
private:
    std::unique_ptr<AbstractImpl> _this;
};

Camera.cpp

#include "Camera.h"
#include "Impl.h"
Camera::Camera(AbstractImpl *impl) : _this(impl){}
Camera::~Camera() = default;
/*And all implementations*/
/*PIMPL Facade function*/
const glm::mat4& Camera::getProjectionMatrix(){
    return _this->getProjectionMatrix();
}

Impl.h

namespace Projection{
    struct Ortographic{};
    struct Perspective{};
}
namespace Behaviour{
    struct FirstPerson{};
    struct FreeLook{};
}
/* Abstract base class for all different implementations */
class Camera::AbstractImpl
{
public:
/* Contains all PIMPL-Idiom members of camera */
/* Contains the pure virtual function for our trait/tag-based method */
virtual const glm::mat4& getProjectionMatrix() = 0;
};

template<typename t_projection, typename t_behaviour>
class Camera::Impl : public Camera::AbstractImpl
{
public:
Impl(){}
const glm::mat4& getProjectionMatrix(){
    return getProjectionMatrix(t_projection{});
}
const glm::mat4& getProjectionMatrix(Projection::Ortographic){
    /* Code for Ortographic Projection */
}
const glm::mat4& getProjectionMatrix(Projection::Perspective){
    /* Code for Perspective Projection */
}
};

And this can be use now in the way

Camera * c = new Camera(new Camera::Impl<Projection::Ortographic,Behaviour::FreeLook>());
c->getProjectionMatrix();

Benefit: This should be mostly statically typed beside the obvious dynamic typing of the AbstractImpl subclasses. It is possible to switch the concrete implementation of a camera during runtime. New Traits can be added very easily. It would be also possible to use this approach as all that is needed is adding subclasses of AbstractImpl.

Drawback: Debugging can become quite a pain. Alone during trying this out i realized, that altough many errors might show during compile/linking time it still is way more difficult to find the problems.

I would appreciat some feedback on solution 4 as well as i added this because of Yaaks implementation. Did i use your suggestion correctly? Did I oversee something or used templates wrongly - as i have not used them before.

Community
  • 1
  • 1
Kevin Streicher
  • 484
  • 1
  • 8
  • 25
  • Could you explain how the accepted solution is a good solution for this problem? Sadly the answer there does not contain any discussion and I do not really see how the answer there is related to the multiple (diamond) inheritance here at all? Edit: Seems like the comment this question relates to was deleted – Kevin Streicher Nov 03 '14 at 18:57

1 Answers1

1
struct ortographic_tag {};
struct perspective_tag {};
struct first_person_tag {};
struct freelook_tag {};

template<class projection_tag, class location_tag>
struct camera_impl : Camera::AbstractImpl {
  virtual const glm::mat4& getProjectionMatrix() override {
    return get_projection_matrix(*this, projection_tag{});
  }
  virtual const glm::vec3& getForwardVector() override {
    return get_forward_vector(*this, location_tag{});
  }
};

template<class camera>
const glm::mat4& get_projection_matrix( camera const& c, ortographic_tag ) {
  // code
}
template<class camera>
const glm::mat4& get_projection_matrix( camera const& c, perspective_tag ) {
  // code
}
template<class camera>
const glm::vec3& get_forward_vector( camera const& c, first_person_tag ) {
  // code
}
template<class camera>
const glm::vec3& get_forward_vector( camera const& c, freelook_tag ) {
  // code
}

camera_impl< x, y > forwards the various virtual methods to non-virtual functions.

If you need to store different data in the _impl depending on projection/location, you could do some traits:

template<class Tag>
struct camera_data;

and store store a camera_data<projection_tag> and camera_data<location_tag> in _impl, but if we are going that far I'd rethink the approach.

We can even go a step further and make our impl just pass every tag to every free function, and let the free function work out what tags to care about.

template<class... Tags>
struct many_tags {};
template<class T0, class...Tags>
struct many_tags:T0, many_tags<Tags...> {};

pass many_tags<a, b, c>, and if they simply overload on tag a it will match. If there are overloads on more than one, you get ambiguity unless they go to the work of handling it themselves. If they want a or b, they have to write custom SFINAE using is_base_of, or wait for concepts lite.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thank you, this is a very interesting approach and I will have to think it through first before selecting it as an answer - although it looks very promising. Also i want to give other users the opportunity to comment on this as well! – Kevin Streicher Nov 03 '14 at 19:13
  • The drawback of this solution would be that it would not be possible to switch the camera during runtime correct? – Kevin Streicher Nov 03 '14 at 20:43
  • @NoxMortem I don't see why. I basically used tag dispatching to generate your 2x2 implementations, and decoupled the class generation from the "method" code by making the class's methods dispatch to free (tag dispatch based) functions. Probably over engineering for a 2x2 case, but it scales up to NxN reasonably well. – Yakk - Adam Nevraumont Nov 03 '14 at 20:59
  • @Yaak Yeah i think i got it now. I have added a possible solution 4 to the question which is based on your answer. The difference is that the methods are part of the inheritance hierarchy which allows to use the pimpl members and it is therefore not necessary to the camera and also the template is not necessary. Did i got it right or made an error. My code compiles and runs, but it could be very likely that i misused templates as intented or that this is not what you intented to suggest at all as i used templates the first time. Can this be considered cumbersome? – Kevin Streicher Nov 04 '14 at 18:03
  • @Yaak, i have marked your answer as correct as I think it answers the question above, altough i have the feeling, templates/tagging very easily will lead to cluttered code and should be used very carefully. – Kevin Streicher Nov 15 '14 at 14:04