¿Cómo crear una interfaz unificada para captadores de una jerarquía de clases heredada?

Creo que cualquier explicación sin el código sería más oscura. Así que aquí está el código donde traté de mantener todo lo más simple posible.

#include <vector>
#include <iostream>

class WithParametersBase
{
public:
    WithParametersBase();

    double getX() const {return 0.0;}
    double getY() const {return 1.0;}

    //let's say I want to access these members using an unified interface:

    double getParameter(int index) const;

    // For example index == 0 means getX and index == 1 means getY.
    // I could implement it for example like this:

protected:

    void addGetter(double (WithParametersBase::* getter)()const)
    {
        getters_.push_back(getter);
    }

    std::vector<double (WithParametersBase::*)()const> getters_;
};

WithParametersBase::WithParametersBase()
{
    addGetter(&WithParametersBase::getX);
    addGetter(&WithParametersBase::getY);
}

double WithParametersBase::getParameter(int index) const
{
    return (this->*(getters_[index]))();
}

De hecho, funciona. Con un programa de prueba:

int main(int argc, char *argv[])
{
   WithParametersBase base;

   std::cout << base.getParameter(0)
             << base.getParameter(1) << std::endl;

   return 0;
}

La impresión es correcta:

01

Pero en caso de que quiera extender esta clase:

class WithParametersDerived : public WithParametersBase
{
public:
    WithParametersDerived();
    double getZ() const  {return 2.0;} // A new getter
};

WithParametersDerived::WithParametersDerived()
{
    // I want to integrate the new getter into the previous interface
    addGetter(&WithParametersDerived::getZ); 
}

para que si llamo:

WithParametersDerived derived;
std::cout << derived.getParameter(2) << std::endl;

quiero conseguir un

2

No puedo compilar el programa. me sale un error:

error: no matching function for call to
'WithParametersDerived::addGetter
(double (WithParametersDerived::*)()const)'

Lo cual es razonable, pero no sé de qué otra manera implementarlo.

Quiero que el creador de la clase derivada pueda simplemente agregar el nuevo getter. Sé que de alguna manera no se siente bien hacer todo esto en tiempo de ejecución, pero no veo una solución de plantilla o una solución de preprocesador. Si tiene algunas sugerencias, por favor hágamelo saber. ¡Cualquier cosa!

preguntado el 04 de julio de 12 a las 05:07

¿Estás tratando de hacer esto por diversión (porque quieres descubrir cómo hacerlo) o por ganancias (en realidad necesitas lograr algo)? -

El doble getParameter(int index) const; La función es utilizada por otras clases en el programa. Estos son conscientes solo de la clase base. Pero si extiendo la clase base, quiero que las otras clases del sistema lo sepan:

mi pregunta estaba más en la línea de: ¿qué propósito más amplio está sirviendo getParameter? Parece el tipo de mecanismo que es propenso a la confusión o a problemas con el tiempo... -

Es decir, este es un problema real que necesito resolver para continuar con mi trabajo. -

El problema real del código es que está utilizando punteros de función miembro de clase y el tipo de función miembro en la clase base no es igual al tipo de función miembro en una clase derivada. No estoy seguro de que sea la mejor opción general, pero podría considerar usar punteros de función que no sean miembros en su lugar y capturar punteros a funciones de clase estáticas. -

1 Respuestas

esquivaré la el porqué necesitas un esquema de este tipo, y concéntrate en el cómo.

En lugar de punteros de función miembro, puede usar un std::function<double ()>, que es un contenedor genérico de cualquier entidad invocable que tenga la firma double foo(). Para crear un std::function<double ()> de una función miembro y una instancia de objeto, se utiliza std::bind como sigue:

std::function<double ()> callback =
    std::bind(&Class::memberFunction, objectInstancePointer);

Si no está utilizando C++ 11, std :: función y std :: enlazar también están disponibles en Boost como boost :: función y boost :: enlazar. La documentación de Boost para estos es en su mayoría (si no en su totalidad) aplicable a sus contrapartes de C++11.

En lugar de un std::vector, puedes usar un std::map para indexar captadores por nombre. Esto puede ser más práctico que mantener una lista central de números de ID de parámetros.

Si sus parámetros pueden ser de diferente tipo que double, entonces es posible que desee considerar el uso de impulso :: cualquiera or impulso :: variante como tipo de retorno.

Aquí hay un ejemplo de trabajo completo usando std::function, std::bind y std::map:

#include <cassert>
#include <map>
#include <iostream>
#include <functional>

class WithParametersBase
{
public:
    WithParametersBase()
    {
        addGetter("X", std::bind(&WithParametersBase::getX, this));
        addGetter("Y", std::bind(&WithParametersBase::getY, this));
    }

    virtual double getX() const {return 0.0;}
    virtual double getY() const {return 1.0;}

    // Access parameter by name
    double getParameter(const std::string& name) const
    {
        auto getterIter = getters_.find(name);
        assert(getterIter != getters_.end());
        return getterIter->second();
    }

protected:
    typedef std::function<double ()> ParameterGetter;
    typedef std::map<std::string, ParameterGetter> GetterMap;

    void addGetter(const std::string& name, const ParameterGetter& getter)
    {
        getters_[name] = getter;
    }

    GetterMap getters_;
};

class WithParametersDerived : public WithParametersBase
{
public:
    WithParametersDerived()
    {
        addGetter("Z", std::bind(&WithParametersDerived::getZ, this));

        // Override base class getX
        addGetter("X", std::bind(&WithParametersDerived::getX, this));
    }

    double getX() const {return 3.0;} 
    double getZ() const {return 2.0;} // A new getter
};

int main(int argc, char *argv[])
{
    WithParametersBase base;
    WithParametersDerived derived;
    WithParametersBase& polymorphic = derived;

    std::cout << base.getParameter("X")
              << base.getParameter("Y")
              << polymorphic.getParameter("X")
              << polymorphic.getParameter("Y")
              << polymorphic.getParameter("Z") << std::endl;

    return 0;
}

La desventaja de este enfoque es que cada instancia de WithParametersBase (o un descendiente) contendrá un GetterMap. Si tiene una gran cantidad de tales objetos, la sobrecarga de memoria de todos esos GetterMaps puede ser indeseable.


Aquí hay una solución más eficiente que elimina std::function y std::bind. Los punteros de función regulares y las funciones de miembros estáticos se utilizan para las devoluciones de llamada de getter. La instancia de objeto para la que se solicita un parámetro se pasa como argumento a estas funciones miembro estáticas. En los tipos derivados, la referencia de la instancia primero se reduce al tipo derivado antes de invocar la función miembro que realiza la obtención real.

ahora solo hay uno GetterMap por clase en lugar de por objeto. Tenga en cuenta el uso de la expresión "construir en el primer uso" en el getters() método para evitar fiasco de orden de inicialización estática.

La desventaja de esta solución es que hay más código repetitivo para escribir para cada clase derivada de WithParametersBase. Podría ser posible reducir la cantidad de código repetitivo usando plantillas (definitivamente sería posible con macros).

#include <cassert>
#include <map>
#include <iostream>

class WithParametersBase
{
public:
    virtual double getX() const {return 0.0;}
    virtual double getY() const {return 1.0;}

    // Access parameter by name
    double getParameter(const std::string& name) const
    {
        auto getterIter = getters().find(name);
        assert(getterIter != getters().end());
        return getterIter->second(*this);
    }

protected:
    typedef double (*ParameterGetter)(const WithParametersBase& instance);
    typedef std::map<std::string, ParameterGetter> GetterMap;

    static double xGetter(const WithParametersBase& instance)
    {
        return instance.getX();
    }

    static double yGetter(const WithParametersBase& instance)
    {
        return instance.getY();
    }

    static GetterMap makeGetterMap()
    {
        GetterMap map;
        map["X"] = &WithParametersBase::xGetter;
        map["Y"] = &WithParametersBase::yGetter;
        return map;
    }

    virtual const GetterMap& getters() const
    {
        // Not thread-safe. Use std::call_once to make thread-safe.
        static GetterMap map = makeGetterMap();
        return map;
    };
};

class WithParametersDerived : public WithParametersBase
{
public:
    double getX() const {return 3.0;} 
    double getZ() const {return 2.0;} // A new getter

protected:
    static double zGetter(const WithParametersBase& instance)
    {
        // It's reasonably safe to assume that 'instance' is of type
        // WithParametersDerived, since WithParametersDerived was the one
        // that associated "Z" with this callback function.
        const WithParametersDerived& derived =
            dynamic_cast<const WithParametersDerived&>(instance);
        return derived.getZ();
    }

    static GetterMap makeGetterMap()
    {
        // We "inherit" the getter map from the base class before extending it.
        GetterMap map = WithParametersBase::makeGetterMap();
        map["Z"] = &WithParametersDerived::zGetter;
        return map;
    }

    virtual const GetterMap& getters() const
    {
        // Not thread-safe. Use std::call_once to make thread-safe.
        static GetterMap map = makeGetterMap();
        return map;
    };
};

int main(int argc, char *argv[])
{
    WithParametersBase base;
    WithParametersDerived derived;
    WithParametersBase& polymorphic = derived;

    std::cout << base.getParameter("X")
              << base.getParameter("Y")
              << polymorphic.getParameter("X")
              << polymorphic.getParameter("Y")
              << polymorphic.getParameter("Z") << std::endl;

    return 0;
}

Respondido 04 Jul 12, 14:07

No estoy seguro de que el nombre sea mejor que el índice. Al final del día, es solo una constante mágica. La ventaja de los índices es que conoce de inmediato el rango de índices disponibles, ¿planea mantener una lista de nombres disponibles? - Matthieu M.

@Emile ¡Muchas gracias por todo! ¡También para sugerencias con std::map y boost::variant! - Martín Drozdik

@MartinDrozdik: consulte la edición para obtener una solución más eficiente (pero con más código repetitivo). - Emilio Cormier

@MatthieuM.: Con los nombres, hay menos dolores de cabeza de mantenimiento en comparación con la asignación de números. Por ejemplo: enum {position=0, speed=1, voltage=2, current=3};. Ahora considere una extensión que le gustaría agregar el acceleration parámetro. Pero tiene que saber que el siguiente número disponible es el 4 y que no lo está usando ya ninguna otra extensión. Es más, acceleration pertenece a las otras medidas espaciales, pero ahora está atascado después de las medidas eléctricas. ¡Puaj! :) - Emilio Cormier

@EmileCormier: Creo que no estamos de acuerdo con el uso de la herramienta. Si necesita la "aceleración", entonces use getAcceleration. Si necesita iterar, entonces use: for (size_t i = 0, max = base.getNbParameters(); ...). Tenga en cuenta que no hay razón para cambiar su enumeración para insertar acceleration después de la velocidad. Si todos usan la enumeración, simplemente funciona :) - Matthieu M.

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.