Bienvenue, Invité. Merci de vous connecter ou de vous inscrire.
Avez-vous perdu votre e-mail d'activation ?

Auteur Sujet: Don't use inheritance directly, use template instead !  (Lu 4977 fois)

0 Membres et 1 Invité sur ce sujet

godefv

  • Newbie
  • *
  • Messages: 1
    • Voir le profil
    • E-mail
Don't use inheritance directly, use template instead !
« le: Août 15, 2014, 04:22:56 pm »
Hello,

The library seems to have a very nice interface.

But one thing can be improved in my opinion : not using inheritance directly, and using template instead ! I strongly encourage you to watch this *really* awsome speech to understand how it works :
"http://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil"

For instance, any client class my_class_t implementing a draw function could be feeded to window.draw() without the need to inherit from Drawable . But there are other very nice advantages (easier to factor code, some performance gain when virtual functions can just be templates, etc).

It is just an advice ;)
Thank you !

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Re : Don't use inheritance directly, use template instead !
« Réponse #1 le: Août 15, 2014, 07:01:49 pm »
Apart from the Drawable interface, I don't think this could be applicable. Inheritance is used at its minimum in SFML.

One big drawback of templates is that code has to be in headers, with all the bad things that it involves (you can't hide it, you potentially expose unwanted dependencies, longer compile times, ...).

Do you have a more concrete use case where this would be better?
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Re : Don't use inheritance directly, use template instead !
« Réponse #2 le: Août 27, 2014, 04:46:46 pm »
I can see one advantage by using a template :

Your library'll be more customizable, by example, if you want that the user can change a part of your library. (With a variadic template class, a wrapper and a macro by example)
He'll be able to change a part of the source code of your library automatically.

I use this mecasim to serialize a lot of different variables type onto different kind of archives in my library.
So I don't need to have to overload the functions for each object type to serialize, it's very paintfull when they are a lot of them.

If the user want to change the library. (by example by creating a class which derive from a class of the library and serialize it)  He can do it with a macro before including your library. (This'll change the class definition thanks to the variadic template and the variadic macro)

But there's also an inconvenient, so this mecanism should be used only if you have no other choices :

-The code'll take more time to compile. (maybe you'll need to use short and inlined functions)
-You'll have to recompile your library automatically if the source code of your library change. (But normally it shoudn't change a lot if the user have a well analized it)

I hope that c++'ll still increase the powerfullness of his meta-programmation system to be able to modify libraries at compile time! (But I don't think that SFML need it, in Laurent Gomila's case, inheritence is normally sufficient)

But anyway I look forward to the futur of the c++ future, this looks so promising, I would like to define some code in my library that the user can modify and some other source code that the user can't modify and change the name of my library to ODCFAEG (Opensource Developement Customizable Framework Adapted For Every Game)


-

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32504
    • Voir le profil
    • SFML's website
    • E-mail
Re : Don't use inheritance directly, use template instead !
« Réponse #3 le: Août 27, 2014, 04:59:17 pm »
(au fait on est sur le forum français hein...)

Les généralités concernant les templates, ainsi que ce que tu fais dans ton framework, ne m'intéressent pas dans cette discussion. Merci de rester focalisé sur SFML et sur des propositions pertinentes et détaillées.
Laurent Gomila - SFML developer

Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Re : Don't use inheritance directly, use template instead !
« Réponse #4 le: Août 27, 2014, 07:51:27 pm »
Ok, je me demandais si c'était possible de faire ça dans ta librairie. (ou pas.)


Lolilolight

  • Hero Member
  • *****
  • Messages: 1232
    • Voir le profil
Re : Don't use inheritance directly, use template instead !
« Réponse #5 le: Septembre 13, 2014, 02:25:27 pm »
Finalement je pense que tu aurais pu utiliser des template, en fait, le principe est très simple :

-Tu enregistres chaque fonction et chaque classe de tes fonctions membres dans une factory en associant à chaque classe et à chaque fonction, un id unique que l'utilisateur (ou bien toi même) utilisera pour appeler la fonction à l'exécution.
Et tu fait une genre de méthode init() comme le fait SDL qui enregistre les fonctions appelée par ta librairie pour dessiner par exemple, bref tu enregistres ce que tu veux quoi. (Il faut passer le nom de la classe en paramètre template à la factory)
Le principe est fort similaire à celui des dll.
Bref moi c'est ce que j'utilise pour faire, un système de réflexion portable.

Ceci à l'avantage que peut importe le nom de la classe de base utilisée pour dessiner, l'utilisateur pourra choisir n'importe quelle type pour la classe de base à condition qu'il l'enregistre dans la factory à l'initialisation de SFML sinon il faudra renvoyer une exception.

Pour le bien tu devrais aussi utiliser std::function pour enregistrer les pointeurs sur tes fonctions de callback, ce qui donnerait quelque chose comme ceci :

#ifndef ODFAEG_FAST_DELEGATE_HPP
#define ODFAEG_FAST_DELEGATE_HPP
#include <functional>
#include <iostream>
#include "export.hpp"
#include "erreur.h"
#include <tuple>
#include <utility>
#include <memory>
/**
  *\namespace odfaeg
  * the namespace of the Opensource Development Framework Adapted for Every Games.
  */

namespace odfaeg {
/**
*  \file  fastDelegate.h
*  \class IRefVal
*  \brief Interface for the warppers to references, values and pointers.
*  This class is used store references, pointers and values to pass to the callack's functions.
*  \param T : the type of the value to wrap.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template<class T>
struct IRefVal {
    /**\fn
    *  \brief default constructor
    */

        IRefVal()=default;
        /**\fn T& get()
        *  \brief get the reference of the wrapped type.
        *  \return the reference of the wrapped type.
        */

        virtual T& get() =0;
        /**\fn std::unique_ptr<IRefVal<T>> clone() const = 0;
        *  \brief copy the wrapper.
        *  \return the copied wrapper.*/

        virtual std::unique_ptr<IRefVal<T>> clone() const = 0;
        /**\fn destructor*/
        virtual ~IRefVal(){}

protected:
    /**\fn IRefVal(const IRefVal&)
    *  \brief constructor, pass the reference to wrap.
    *  \param const IRefVal& : the reference to wrap.
    */

        IRefVal(const IRefVal&){}
        /** \fn IRefVal& operator=(const IRefVal&)
        *   \brief affector.
        *   \param const IRefVal& : the wrapper to affect.
        *   \return the affected wrapper.*/

        IRefVal& operator=(const IRefVal&)
        { return *this; }
};
/**
*  \file  fastDelegate.h
*  \class Ref
*  \brief Warp a reference. (Use std::reference_wrapper so we can pass a reference with std::ref)
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template<class T>
struct Ref : IRefVal<T> {
    /**
    *\fn Ref(const std::reference_wrapper<T>& r)
    *\brief Constructor : pass an std::reference_wrapper to the wrapper.
    *\param std::reference_wrapper<T>& r : the reference_wrapper.
    */

        Ref(const std::reference_wrapper<T>& r)
                : ref(r)
        {}
        /**
        * \fn T& get()
        * \brief return a reference to an object.
        * \return T& the reference to the object.
        */

        T& get()
        { return ref.get(); }
        /**
        * \fn std::unique_ptr<IRefVal<T>> clone() const
        * \brief copy the reference wrapper.
        * \return std::unique_ptr<IRefVal<T>> : the cloned wrapper.
        */

        std::unique_ptr<IRefVal<T>> clone() const
        { return std::make_unique<Ref>(*this); }
private:
        std::reference_wrapper<T> ref; /**> the std::reference_wrapper which warp the reference.*/
};
/**
*  \file  fastDelegate.h
*  \class Val
*  \brief Warp a value.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template<class T>
struct Val : IRefVal<T> {
    /**\fn Val(const T& t)
    *  \brief pass a value to the wrapper.
    *  \param const T& t : the value to pass.
    */

        Val(const T& t)
                : val(t)
        {}
        /** \fn
        *   \brief return the value
        *   \return T& : return the value.
        */

        T& get()
        { return val; }
        /**
        * \fn std::unique_ptr<IRefVal<T>> clone() const
        * \brief copy the value wrapper.
        * \return std::unique_ptr<IRefVal<T>> : the cloned wrapper.
        */

        std::unique_ptr<IRefVal<T>> clone() const
        { return std::make_unique<Val>(*this); }
private:
        T val; /**> T val : keep the value of the wrapper.*/
};
/**
*  \file  fastDelegate.h
*  \class Ref
*  \brief Warp a pointer.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template<class T>
struct Pointer : IRefVal<T> {
    /**\fn Pointer(const T* t)
    *  \brief pass a pointer to the wrapper.
    *  \param const T* t : the pointer to pass.
    */

        Pointer(const T* t)
                : pointer(t)
        {}
        /** \fn
        *   \brief return the pointer of the wrapper.
        *   \return T& : return the pointer.
        */

        T& get()
        { return pointer; }
        /**
        * \fn std::unique_ptr<IRefVal<T>> clone() const
        * \brief copy the pointer wrapper.
        * \return std::unique_ptr<IRefVal<T>> : the cloned wrapper.
        */

        std::unique_ptr<IRefVal<T>> clone() const
        { return std::make_unique<Pointer<T>>(*this); }

private:
        T pointer; /**> T pointer : keep the pointer of the wrapper.*/
};
/**
*  \file  fastDelegate.h
*  \class RefVal
*  \brief Wrap a pointer, a value or a reference an keep a pointer to the generic wrapper.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template<class T>
struct RefVal {
    /**
    * \fn RefVal (const T& t)
    * \brief constructor : construct a wrapper to a value
    * \param const T& t : the value to
    */

        RefVal(const T& t)
        : rv(std::make_unique<Val<T>>(t))
        {}
        RefVal(const std::reference_wrapper<T>& r)
        : rv(std::make_unique<Ref<T>>(r))
        {}
        RefVal(const T*& p)
        : rv(std::make_unique<Pointer<T>>(p))
        {}
        RefVal(const RefVal& rhs)
    {
        rv = rhs.rv->clone();
    }
        RefVal& operator=(const RefVal& rhs)
        { rv=rhs.rv->clone(); return *this; }
        T& get() const
        { return rv->get(); }

private:
        std::unique_ptr<IRefVal<T>> rv;
};
//Classe de trait pour déterminer le type à stocker

//(Interne) Cas général
template<class T>
struct ToStoreImpl
{ using type = T; };

template<class T>
struct ToStoreImpl<std::reference_wrapper<T>>
{ using type = T; };

template<class T>
struct ToStore
        : ToStoreImpl<std::remove_reference_t<T>>
{};

template<class T>
using ToStore_t = typename
        ToStore<T>::type;
template<class R, class C, class... ArgT>
struct DynamicWrapper {
        DynamicWrapper(R(C::*pf)(ArgT...)) : pfunc(pf){}
        template<class O, class... ArgU>
        R operator()(O* o, ArgU&&... arg) const
        {
                if(dynamic_cast<C*>(o))
                        return (dynamic_cast<C*>(o)->*pfunc)(std::forward<ArgU>(arg)...);
        throw Erreur(0, "Invalid cast : types are nor polymorphic!", 1);
        }
private:
        R (C::*pfunc)(ArgT...);
};
template<class F>
class DynamicFunction;

template<class R, class... ArgT>
class DynamicFunction<R(ArgT...)>
        : std::function<R(ArgT...)>
{
        using Base = std::function<R(ArgT...)>;

public:
        template<class F>
        DynamicFunction(F&& f) : Base(std::forward<F>(f))
        {}
        template<class C, class... ArgU>
        DynamicFunction(R (C::*pf)(ArgU...))
                : Base(DynamicWrapper<R,C,ArgU...>(pf))
        {}
    using Base::operator();
};
template<class R>
struct Delegate {
        Delegate() =default;
        virtual std::unique_ptr<Delegate> clone() const = 0;
        virtual R operator()() = 0;
        virtual ~Delegate(){}

protected:
        Delegate(const Delegate&){}
        Delegate& operator=(const Delegate&)
        { return *this; }
};

template<class R, class... ArgT>
struct FastDelegateImpl : Delegate<R> {
    template<class F, class... ArgU>
        FastDelegateImpl(F&& f, ArgU&&... arg)
                : func(std::forward<F>(f))
                , param(std::forward<ArgU>(arg)...)
        {}
        std::unique_ptr<Delegate<R>> clone() const
        { return std::make_unique<FastDelegateImpl>(*this); }
        R operator()()
        { return call(std::make_index_sequence<sizeof...(ArgT)>()); }
        template<class... ArgU>
        void setParams(ArgU&&... arg)
        { param=std::make_tuple(std::forward<ArgU>(arg)...); }

private:
        template<std::size_t... I>
        R call(std::index_sequence<I...>)
        { return func(std::get<I>(param).get()...); }

        std::function<R(ArgT&...)> func;
        std::tuple<RefVal<ArgT>...> param;
};
template<class R>
struct FastDelegate {
    FastDelegate() = default;
    template<class F, class... Arg>
        FastDelegate(F&& f, Arg&&... arg) :
                delegate(std::make_unique
                        <FastDelegateImpl<R,ToStore_t<Arg>...>>
                        (std::forward<F>(f),std::forward<Arg>(arg)...)
                )
        {}
        FastDelegate(FastDelegate& rhs)
                : delegate(rhs.delegate->clone())
        {}
        FastDelegate(const FastDelegate& rhs)
                : delegate(rhs.delegate->clone())
        {}
        FastDelegate(FastDelegate&& rhs) =default;
        FastDelegate& operator=(const FastDelegate& rhs)
        { return operator=(FastDelegate(rhs)); }
        FastDelegate& operator=(FastDelegate&&) =default;
        R operator()() const
        { return (*delegate)(); }
        template<class... Arg>
        void setParams(Arg&&... arg)
        {
                using StaticType =
                        FastDelegateImpl<R,ToStore_t<Arg>...>*;
                static_cast<StaticType>(delegate.get())->setParams(std::forward<Arg>(arg)...);
        }
private:
        std::unique_ptr<Delegate<R>> delegate;
};
}
#endif
 

//fichier fabrique.h
#ifndef ODFAEG_FACTORY_HPP
#define ODFAEG_FACTORY_HPP
#include <map>
#include <string>
#include "export.hpp"
#include "fastDelegate.h"
/**\file factory.h
*  \brief This file register types of derived objects
*  and instanciate the polymorphic objects of the right type with the given type id.
*  There are two factories
*  The static factory (The class Factory) which evaluate objects and functions at compile time.
*  The dynamic factory (The class BaseFactory) which evaluate objects and functions at runtime.
*  The dynamic factory avoids to have a reflexion system which is impossible to make in c++ without a lot of macros.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

/********************************************************************************************************************/
/**\fn
* \brief This is an helper function like macro which register a derived type in the dynamic factory.
* \param ID : an ID which is associate to a derived type.
* \param BASE : the base type of the derived class.
* \param DERIVED : the derived type of the derived class.
*/

#define REGISTER_TYPE(ID, BASE, DERIVED) \
{ \
DERIVED *derived##ID = nullptr; \
odfaeg::Allocator<BASE> allocator##ID; \
BASE*(odfaeg::Allocator<BASE>::*f##ID)(DERIVED*) = &odfaeg::Allocator<BASE>::allocate<DERIVED>; \
odfaeg::FastDelegate<BASE*> allocatorDelegate##ID(f##ID, &allocator##ID, derived##ID); \
odfaeg::BaseFact<BASE>::register_type(typeid(DERIVED).name(), allocatorDelegate##ID); \
}

/**fn
* \brief This is an helper function like macro which register a function in the dynamic factory.
* \param ID : an ID which is associate to a derived type.
* \param funcName : the name of the derived class member's function to register.
* \param SID : an ID associated to the argument list of the member's function to register.
* \param BASE : the base type of the derived class.
* \param DERIVED : the derived type of the derived class.
* \param SIGNATURE : the signature of the function to register.
*/

#define REGISTER_FUNC(ID, funcName, SID, BASE, DERIVED, SIGNATURE, args...) \
{ \
REGISTER_TYPE(ID, BASE, DERIVED) \
void(DERIVED::*f##ID##funcName##SID)SIGNATURE = &DERIVED::vt##funcName; \
odfaeg::FastDelegate<void> delegate##ID##funcName##SID (f##ID##funcName##SID, args); \
odfaeg::BaseFact<BASE>::register_function(typeid(DERIVED).name(), #funcName, #SID, delegate##ID##funcName##SID); \
}

/**
  *\namespace odfaeg
  * the namespace of the Opensource Development Framework Adapted for Every Games.
  */

namespace odfaeg {
/**\class Factory
*  \brief This class register types of derived objects
*  and instanciate the polymorphic objects of the right type with the given type id.
*  This is a static factory, it means that the id is defined at compile time.
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template <class O, class K = std::string>
class Factory {
private :
     static std::map<K, O*> m_map;
public:
     static void Register (K key, O* object);
     O* Create (const K& key);
};
/**\class Allocator
*  \brief this struct allocate an object of a derived type and return a pointer of the base type.
*  \param B : the base type.
*/

template <typename B>
struct Allocator {
   /**\fn allocate(D*)
   *  \brief this function allocates an object of a derived type and return a pointer of the base type.
   *  \param D : the derived type.
   *  \return B* : a pointer to the base type.
   */

   template <typename D>
   B* allocate(D*) {
        return new D();
   }
};
/**\class BaseFactory
*  \brief This class register types of derived objects
*  and instanciate the polymorphic objects of the right type with the given type id.
*  This is a dynamic factory, it means that the id is defined at runtime time with RTTI infos.
*  So you must provide the id with typeid(object).name().
*  \author Duroisin.L
*  \version 1.0
*  \date 1/02/2014
*/

template <typename B>
class BaseFactory {
    public :
    /**
    * \fn void register_type (std::string typeName, FastDelegate<B*> allocatorDelegate)
    * \brief register a type into a factory, if the type isn't already registered.
    * \param std::string typeName : the name of the type to register.
    * \param a callback function to an allocator, to return a pointer of the base class which point
    * to the derived object.
    */

    static void register_type(std::string typeName, FastDelegate<B*> allocatorDelegate) {
        typename std::map<std::string, FastDelegate<B*>>::iterator it = types.find(typeName);
        if (it == types.end()) {
            types[typeName] = allocatorDelegate;
        }
    }
    /** \fn void register_function(std::string typeName, std::string funcName, std::string funcArgs, FastDelegate<void> delegate)
    *   \brief register a member function of a class type into the factory.
    *   \param std::string typeName : the type name of the class containing the member function to register.
    *   \param std::string funcName : the name of the function to register.
    *   \param std::string funcArgs : the name of the types of the member function's argument list to register.
    *   \param FastDelegate<void> delegate : a callback function of the registered member function. (only funcions returing void can be registered!)
    */

    static void register_function(std::string typeName, std::string funcName, std::string funcArgs, FastDelegate<void> delegate) {
        typename std::map<std::string, FastDelegate<void>>::iterator it = functions.find(typeName+funcName+funcArgs);
        if (it == functions.end())
            functions[typeName+funcName+funcArgs] = delegate;
    }
    /** \fn void callFunction(std::string typeName, std::string funcName, std:string fincArgs, A&&... args)
    *   \brief call a registered function of the factory, throw an error if the function isn't registered.
    *   \param std::string typeName : the type name of the class containing the member function.
    *   \param std::string funcName : the name of the member function.
    *   \param std::string funcArgs : the name of the types of the member function's argument list to call.
    *   \param A&& args.... : the value of the arguments to pass to the callback's member function. (object + arguments)
    */

    template <typename... A>
    static void callFunction(std::string typeName, std::string funcName, std::string funcArgs, A&&... args) {
        typename std::map<std::string, FastDelegate<void>>::iterator it = functions.find(typeName+funcName+funcArgs);
        if (it != functions.end()) {
            it->second.setParams(std::forward<A>(args)...);
            (it->second)();
        } else {
            throw Erreur(0, "Unregistred function exception!", 1);
        }
    }
    /** \fn B* create (std::string typeName)
    *   \brief return a pointer of the base class of an object which point to an object of a derived type.
    * this function call a callback function to an allocator function to allocate the object.
    *   \param the typeName of the type to allocate.
    *   \return B* a pointer of a base class which'll point to the allocated object.
    */

    static B* create (std::string typeName) {
        typename std::map<std::string, FastDelegate<B*>>::iterator it = types.find(typeName);
        if (it != types.end()) {
            return (it->second)();
        }
        throw Erreur(0, "Unregistred type exception!", 1);
    }
    /** \fn std::string getTypeName (B* type)
    *   \brief return the type name of a base object.
    *   \param B* type : a pointer to the type to check the dynamic type name.
    *   \return the dynamic type name of the passed object*/

    static std::string getTypeName (B* type) {
        typename std::map<std::string, FastDelegate<B*>>::iterator it = types.find(typeid(*type).name());
        if (it != types.end())
            return it->first;
    }
    private :
    static std::map<std::string, FastDelegate<B*>> types; /**> An std::map which store the typeName and a callback's function to an allocator of the registered types*/
    static std::map<std::string, FastDelegate<void>> functions; /**> An std::map which store the signature and a callback's function to the registered member's functions.*/
};
template <typename B>
std::map<std::string, FastDelegate<B*>> BaseFactory<B>::types = std::map<std::string, FastDelegate<B*>>();
template <typename B>
std::map<std::string, FastDelegate<void>> BaseFactory<B>::functions = std::map<std::string, FastDelegate<void>>();
}
#endif
 

Ensuite tu n'as plus qu'à faire une fonction SFML_Init() pour enregistrer toutes les classes et fonctions template utiliser par SFML.

Donc je pense que tu pourrais améliorer SFML à l'avenir, et utiliser le c++14. ;) (Ne t'inquiète pas pour la compatibilité, normalement tout les PC supportent gcc4.9 et d'aileurs c'est la version de gcc qu'on devrait tous utiliser pour des raisons de sécurité, et éviter les nombreux memory leaks et problème d'UB présent encore dans certaines libs écrite en C utilisant SFML mais aussi parfois dans SFML.
« Modifié: Septembre 13, 2014, 02:33:47 pm par Lolilolight »