dimanche 28 août 2016

C++ private polymorphic implementation design

This is a C++(11) question. I have a object Obj myObj encapsulating an object f of type MyType. Depending on runtime context, the object fshould behave differently.

One natural way of implementing this would be for the class Obj to encapsulate a pointer to an abstract base class MyType, which would, depending on the context point to different (public) child of MyType, such as MyType1, MyType2, etc.

However, I'm not very keen on Obj "suffering" the consequences of MyType being polymorphic, i.e. having to deal with a pointer. In particular, if I make it a std::unique_ptr<MyType>, it implies that Obj can either not be copied or that one needs to give it a proper copy constructor that deals with copying MyType ressources.

In my opinion, MyType being polymorphic shouldn't be Obj's problem.

I came with the following classes. Essentially the idea is to hide the pointer within MyTypeprivate attributes. In addition my second question concerns the fact that concrete implementations of MyTypeImpl may share some code shouldn't be repeated. I've put that in a class from which concrete implementations privately inherit.

I'm curious what more expert developers than me would think about it. Is it too heavy "just to hide the pointer"? Is there a better way to do it?

#include <iostream>
#include <memory>


// a "standard" implementation of MyType
class MyTypeImpl
{
    public:        
        virtual double operator()(double a) = 0;
        virtual int implType() const = 0;
        virtual void complexStuff() const = 0;
};



// some internal stuff common to all implementations
class MyTypeImplInternals
{
protected:    
    MyTypeImplInternals(int value):factor_{value}{}
    int factor_;
    void longCommonFunction() const{ std::cout << "I'm doing complex stuff common to all interfaces " << factor_ << "\n" ;}
};


// one specific implementation
class MyTypeImpl1: public MyTypeImpl, private MyTypeImplInternals
{
    public:
        MyTypeImpl1(int factor):MyTypeImplInternals{factor}{};

        virtual double operator()(double a) override {return factor_*a;} 
        virtual int implType() const override {return 1;}
        virtual void complexStuff() const override { longCommonFunction(); }
};


// a second implementation
class MyTypeImpl2: public MyTypeImpl, private MyTypeImplInternals
{
    public:
        MyTypeImpl2(int factor):MyTypeImplInternals{factor}{};
        virtual double operator()(double a) override {return factor_*a;} 
        virtual int implType() const override {return 2;}
        virtual void complexStuff() const override { longCommonFunction(); }
};



class MyTypeImplFactory
{
    public:
    static std::unique_ptr<MyTypeImpl>createMyTypeImpl(int implementationType)
    {        
        switch(implementationType)
        {
            case 1:            
              return std::unique_ptr<MyTypeImpl> (new MyTypeImpl1(12));

             case 2:
                return std::unique_ptr<MyTypeImpl> (new MyTypeImpl2(22));

            default:
                throw std::runtime_error("implementation does not exist...\n");
                return nullptr;
        }
    }
};



// my type
class MyType
{
    public:

        MyType(int implementationType)
        {
            implPtr_ = MyTypeImplFactory::createMyTypeImpl(implementationType);   
        }

        MyType(MyType const& source)
        : implPtr_{ MyTypeImplFactory::createMyTypeImpl(source.implType()) }
        {
        }


        double operator()(double a){return (*implPtr_)(a);}  
        int implType() const {return implPtr_->implType();}
        void complexStuff() const {implPtr_->complexStuff();}


    private:
        std::unique_ptr<MyTypeImpl> implPtr_;
};








class Obj
{
 private:
    MyType f;

public:
    Obj(int dim):f{dim}{}    
    Obj(Obj&& sourceToMove) = default;
    Obj(Obj const& source) = default;

    void doStuff() {std::cout << "I'm doing stuff()  " << f(2) << std::endl; f.complexStuff();}
};







int main()
{
 Obj myObj{1}, myObj2{2};

 myObj.doStuff();
 myObj2.doStuff();

 Obj myObj3{std::move(myObj2)}; // myObj2 now dead
 Obj myObj4{myObj}; 

 myObj3.doStuff();
 myObj4.doStuff();

}

link to online compiler : http://cpp.sh/8rhyy

Here the implementations are very dumb ones to serve as an example. An application for this design could be for instance a Solver (Obj) that solves some kind of physics Equation (MyType) which exact definition depends on the dimensionality of the problem, equation in 1D space is not the same as in 2D or in 3D. Solver's code would be completely independent on Equation's dimensionality and also wouldn't have to deal with a pointer. Equation would hide its 1D, 2D, or 3D implementation from outside's world.

(was originally a post on code review that was put on Hold because to abstract)

Aucun commentaire:

Enregistrer un commentaire