Quelles sont les alternatives à cette hiérarchie de classe basée typelist code de génération?

StackOverflow https://stackoverflow.com/questions/4735500

Question

Je travaille avec un modèle objet simple dans lequel les objets peuvent implémenter des interfaces pour fournir des fonctionnalités en option. À son cœur, un objet doit mettre en œuvre une méthode de getInterface qui est donné un identifiant d'interface (unique). La méthode renvoie alors un pointeur vers une interface - ou nulle, dans le cas où l'objet ne met pas en oeuvre l'interface demandée. Voici une esquisse de code pour illustrer ceci:

struct Interface { };
struct FooInterface : public Interface { enum { Id = 1 }; virtual void doFoo() = 0; };
struct BarInterface : public Interface { enum { Id = 2 }; virtual void doBar() = 0; };
struct YoyoInterface : public Interface { enum { Id = 3 }; virtual void doYoyo() = 0; };

struct Object {
    virtual Interface *getInterface( int id ) { return 0; }
};

Pour faciliter les choses pour les clients qui travaillent dans ce cadre, j'utilise un petit modèle qui génère automatiquement la mise en œuvre du « GetInterface » afin que les clients ont juste pour mettre en œuvre les fonctions réelles requises par les interfaces. L'idée est d'obtenir un type de béton de Object ainsi que toutes les interfaces et laisser getInterface simplement renvoyer des pointeurs this (casté en type à droite). Voici le modèle et l'utilisation de la démonstration:

struct NullType { };
template <class T, class U>
struct TypeList {
    typedef T Head;
    typedef U Tail;
};

template <class Base, class IfaceList>
class ObjectWithIface :
    public ObjectWithIface<Base, typename IfaceList::Tail>,
    public IfaceList::Head
{
public:
    virtual Interface *getInterface( int id ) {
        if ( id == IfaceList::Head::Id ) {
            return static_cast<IfaceList::Head *>( this );
        }
        return ObjectWithIface<Base, IfaceList::Tail>::getInterface( id );
    }
};

template <class Base>
class ObjectWithIface<Base, NullType> : public Base
{
public:
    virtual Interface *getInterface( int id ) {
        return Base::getInterface( id );
    }
};

class MyObjectWithFooAndBar : public ObjectWithIface< Object, TypeList<FooInterface, TypeList<BarInterface, NullType> > >
{
public:
    // We get the getInterface() implementation for free from ObjectWithIface
    virtual void doFoo() { }
    virtual void doBar() { }
};

Cela fonctionne très bien, mais il y a deux problèmes qui sont laids:

  1. Un bloqueur pour moi est que cela ne fonctionne pas avec MSVC6 (qui a un mauvais support pour les modèles, mais je dois malheureusement pour le soutenir). MSVC6 donne une erreur C1202 lors de la compilation cela.

  2. Toute une gamme de classes (une hiérarchie linéaire) est généré par le modèle de ObjectWithIface récursive. Ce n'est pas un problème pour moi en tant que tel, mais malheureusement je ne peux pas faire une seule déclaration switch pour mapper un ID d'interface à un pointeur dans getInterface. Au lieu de cela, chaque étape dans les contrôles de la hiérarchie pour une seule interface et transmet ensuite la demande à la classe de base.

Quelqu'un at-il des suggestions pour améliorer cette situation? Soit en fixant les deux problèmes ci-dessus avec le modèle de ObjectWithIface, ou en proposant des solutions de rechange qui rendrait l'objet / cadre interface plus facile à utiliser.

Était-ce utile?

La solution

Qu'en est-il quelque chose comme ça?

struct Interface
{
    virtual ~Interface() {}
    virtual std::type_info const& type() = 0;
};

template <typename T>
class InterfaceImplementer : public virtual Interface 
{
    std::type_info const& type() { return typeid(T); }
};

struct FooInterface : InterfaceImplementer<FooInterface>
{
    virtual void foo();
};

struct BarInterface : InterfaceImplementer<BarInterface>
{
    virtual void bar();
};

struct InterfaceNotFound : std::exception {};

struct Object
{
    void addInterface(Interface *i)
    {
        // Add error handling if interface exists
        interfaces.insert(&i->type(), i);
    }

    template <typename I>
    I* queryInterface()
    {
        typedef std::map<std::type_info const*, Interface*>::iterator Iter;
        Iter i = interfaces.find(&typeid(I));
        if (i == interfaces.end())
            throw InterfaceNotFound();

        else return static_cast<I*>(i->second);
    }

private:
    std::map<std::type_info const*, Interface*> interfaces;
};

Vous voudrez peut-être quelque chose de plus élaboré que type_info const* si vous voulez faire à travers les bibliothèques dynamiques frontières. Quelque chose comme std::string et type_info::name() fonctionnera très bien (mais un peu lent, mais ce genre d'expédition extrême aura probablement besoin lent quelque chose). Vous pouvez également fabriquer des identifiants numériques, mais cela est peut-être plus difficile à maintenir.

Stockage de hash type_infos est une autre option:

template <typename T>
struct InterfaceImplementer<T>
{
    std::string const& type(); // This returns a unique hash
    static std::string hash(); // This memoizes a unique hash
};

et l'utilisation FooInterface::hash() lorsque vous ajoutez l'interface et le Interface::type() virtuel lorsque vous interrogez.

Autres conseils

dynamic_cast existe dans la langue pour résoudre ce problème exact.

Exemple d'utilisation:

class Interface { 
    virtual ~Interface() {} 
}; // Must have at least one virtual function
class X : public Interface {};
class Y : public Interface {};

void func(Interface* ptr) {
    if (Y* yptr = dynamic_cast<Y*>(ptr)) {
        // Returns a valid Y* if ptr is a Y, null otherwise
    }
    if (X* xptr = dynamic_cast<X*>(ptr)) {
        // same for X
    }
}

dynamic_cast va également gérer de façon transparente des choses comme l'héritage multiple et virtuel, que vous pouvez bien lutter avec.

Edit:

Vous pouvez vérifier la QueryInterface de COM pour this- ils utilisent un design similaire avec une extension du compilateur. Je ne l'ai jamais vu code COM mis en œuvre, utilisé uniquement les en-têtes, mais vous pouvez rechercher.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top