2012-02-18 12 views
5

Estoy tratando de configurar un azúcar sintáctico similar al concepto de propiedad C#.C++ derive pure abstract w/estructura anidada

He leído esta publicación: C#-like properties in native c++?. Es útil, pero carece del diseño que quiero. También estoy respetuosamente en desacuerdo con varias de las afirmaciones hechas allí de que el concepto de propiedad es malo oo, ya que no veo la diferencia entre un conjunto de métodos titulados get_id() y set_id() y una sobrecarga del operador que expone el mismo concepto, permite que el código esté más limpio cuando se consume la clase, y generalmente desalienta el acceso público a los campos privados, también desacopla la implementación pública del diseño privado.

Sin embargo, el código que he ideado (inspirado por ese enlace) es REALMENTE kludgy y será bastante difícil de mantener. Me pregunto si alguien tiene alguna sugerencia para aclarar esto o, lo que es más importante, sabe una mejor manera de hacerlo.

class someClass 
{ 
public: 
    struct someProperty_struct{ 
     virtual someProperty_struct& operator=(const int&){return *this;} 
     virtual operator int(){return 0;} 
    }someProperty; 
}; 

class derivedClass : someClass 
{ 
    int i; 
public: 
    struct intProperty: someClass::someProperty_struct 
    { 
    protected: 
     friend class derivedClass; 
     derivedClass *outer; 
    public: 
     virtual someProperty_struct& operator=(const int& value){ 
      outer->i = value; 
      return *this;} 
     virtual operator int(){return outer->i;} 
    }; 
    derivedClass() 
    { 
     intProperty p = intProperty(); 
     p.outer = this; 
     someProperty = p; 
    } 
}; 

class consumerClass 
{ 
public: 
    someClass* data; 
    consumerClass() 
    { 
     data = (someClass*)(new derivedClass()); 
    } 
    void doSomething() 
    { 
     data->someProperty = 7; 
     int x = data->someProperty; 
    } 
}; 

EDIT 1: Se me ocurre que no revela nada acerca de mis intenciones con este. Se usará en un programa de programación, y vamos a hacer una gran cantidad de comparación y asignación de todos los datos en las clases. 'someClass' será efectivamente una interfaz sobre los datos (se necesitan muy pocos métodos, muchos datos que deberían ser relativamente transparentes). Se definirá en una biblioteca estática a la que se vinculará el ejecutable. 'derivedClass' será efectivamente una implementación externa, implementada en un DLL que se cargará dinámicamente. La razón para esto es habilitar el "intercambio en caliente" del dll con otro que implementa un backend de archivo diferente. Tenemos planes para implementar backends de almacenamiento xml, sqlite y mysql usando un sistema de complemento para cargarlos.

Básicamente, necesito un diseño que permita que someClass sea una interfaz virtual heredada por derivedClass, que se carga por el método de fábrica, se pasa a través del sistema de complementos y al final usa consumerClass.

+0

Así, después de mucha reflexión, he encontrado el problema: que no has dicho lo que carece de la solución propuesta: en su caso, carece de una forma de especificar un captador costumbre y un regulador de encargo . –

+0

¿Te refieres a mi solución propuesta, o la que está en el enlace al que se hace referencia? El punto de esto es que someClass será virtualmente puro virtual, que luego será derivado e implementado por alguna otra biblioteca, de ahí la necesidad de pura virtualidad y herencia. El getter y setter proporcionado en intProperty en mi implementación es personalizado, y se puede manipular según sea necesario para proporcionar todo tipo de herramientas útiles (en mi caso, planeo usarlo para la carga diferida desde un archivo xml) – lassombra

+0

OK, entonces Entendiste mal. Me preguntaba por qué no usaste la solución de plantilla simple propuesta en la pregunta que mencionaste. Lo que necesita es una propiedad como parte de una interfaz. Eso no es lo que estaba tratando de hacer. Veré si tengo una idea. –

Respuesta

1

Así que, básicamente, necesito un diseño que permite algunaClase a ser un interfaz virtual

Si he entendido bien, esta intención contradice la solución que ha ocurrió. La herencia virtual concierne solo a las funciones de miembros virtuales. Y no hay forma de que pueda heredar estructuras anidadas dinámicamente porque es un tema de la metaprogramación léxica que no se admite en C++.

Mi sugerencia es pensar detenidamente si puede hacer que su objeto sea inmutable. Entonces, alguna adaptación del patrón de diseño Builder/Factory le permitirá eliminar el uso de propiedades.

También puede considerar escribir un generador de código. En caso de que elija una buena palabra clave de marcado, puede ser fácil.

ACTUALIZACIÓN He agregado un código para aclarar mi sugerencia.

Antes que nada preparamos algunos objetos auxiliares. Deben colocarse en un archivo de encabezado accesible tanto para la Biblioteca como para el Cliente. Estos objetos nunca serán modificados.

GetSetProp <> ----> IGetSetProp < ----- PropFunctionAdapter

template<class _Ty> 
struct __declspec(novtable) IGetSetProp 
{ 
    typedef std::tr1::shared_ptr<IGetSetProp<_Ty>> ptr_t; 
    virtual void set_Prop(_Ty const& val); 
    virtual _Ty get_Prop() const; 
}; 

template<typename _Ty> 
class PropFunctionAdapter : public IGetSetProp<_Ty> 
{ 
    std::function<_Ty(void)> getter; 
    std::function<void(_Ty const&)> setter; 
public: 
    PropFunctionAdapter(std::function<_Ty(void)> _getter, std::function<void(_Ty const&)> _setter) 
     : getter(_getter) 
     , setter(_setter) 
    { 
     // One may want to verify that getter and setter are not empty 
    } 

    virtual ~PropFunctionAdapter() throw() {} 

    inline static std::tr1::shared_ptr<typename PropFunctionAdapter<_Ty>> Create(std::function<_Ty(void)> _getter, std::function<void(_Ty const&)> _setter) 
    { 
     return std::make_shared<typename PropFunctionAdapter<_Ty>>(_getter, _setter); 
    } 

public: 
    void set_Prop(_Ty const& val) 
    { 
     setter(val); 
    } 

    _Ty get_Prop() const 
    { 
     return getter(); 
    } 
}; 

template<class _Owner, class _Ty> 
typename IGetSetProp<_Ty>::ptr_t CreateAdapter(_Owner& source, _Ty(_Owner::*getter)() const, void(_Owner::*setter)(_Ty const&)) 
{ 
    return PropFunctionAdapter<_Ty>::Create(
     std::tr1::bind(std::mem_fn(getter), &source), 
     std::tr1::bind(std::mem_fn(setter), &source, std::tr1::placeholders::_1)); 
} 

template<class _Ty> 
class GetSetProp 
{ 
    typename IGetSetProp<_Ty>::ptr_t prop; 
public: 
    GetSetProp(typename IGetSetProp<_Ty>::ptr_t _prop) 
     : prop(_prop) 
    { 
    } 

    GetSetProp<_Ty>& operator= (_Ty const& val) 
    { 
     prop->set_Prop(val); 
     return *this; 
    } 

    operator _Ty() const 
    { 
     return prop->get_Prop(); 
    } 
}; 

Del mismo modo se puede definir GetProperty y SetProperty.

Supongamos que tiene un contrato de datos que contiene dos campos Presión: int y Descripción: cadena. A continuación, se define el contrato de datos:

class BaseClass 
{ 
public: 
    GetSetProp<int> Pressure; 
    GetSetProp<std::string> Description; 
protected: 
    BaseClass(IGetSetProp<int>::ptr_t fieldA, IGetSetProp<std::string>::ptr_t fieldB) 
     : Pressure(fieldA) 
     , Description(fieldB) 
    { 
    } 

    virtual ~BaseClass() throw() {} 
}; 

y su aplicación en la biblioteca:

class DerivedClass : public BaseClass 
{ 
public: 
    // Here you initialize fields assigning them correspondent setters and getters 
    // You may define an ATL-like mapping in order to hide implementation details 
    DerivedClass() 
     : BaseClass(
      CreateAdapter(*this, &DerivedClass::get_Pressure, &DerivedClass::set_Pressure) 
      , CreateAdapter(*this, &DerivedClass::get_Description, &DerivedClass::set_Description)) 
    { 
    } 

    virtual ~DerivedClass() throw() {} 

private: 
    void set_Pressure(int const& value) 
    { 
     val = value; 
    } 

    int get_Pressure() const 
    { 
     return val; 
    } 

    void set_Description(std::string const& description) 
    { 
     this->description = description; 
    } 

    std::string get_Description() const 
    { 
     return description; 
    } 

private: 
    int val; 
    std::string description; 
}; 

// The client-side code 
DerivedClass d; 
BaseClass& b = d; 
b.Description = "Hello"; 
std::string descr = b.Description; 
b.Pressure = 2; 
int value = b.Pressure; 
+0

El problema con esto es que ha negado por completo la intención declarada del diseño. La intención es que someClass sea efectivamente un contrato de datos, completo con propiedades como constructos. derivedClass sería la implementación * oculta * de ese contrato, implementado a través de una fábrica en un contexto cargado dinámicamente – lassombra

+0

He actualizado la respuesta para aclarar mi punto –

+0

Tuve que volver a leer esa definición de código 3 veces antes de que realmente recogiera lo que era pasando. Dudo un poco en confiar en eso debido a que quita el vtable en una parte, y que el diseño deja muchos detalles de implementación en la clase base (lo que permite que la clase derivada solo proporcione los accesadores reales). Sin embargo, parece que logra casi exactamente lo que quiero con la sobrecarga del operador ganando acceso a métodos internos y luciendo agradable en diseño. – lassombra

1

Creación de una clase de ayuda propiedad que debe ser instanciado como un miembro de la clase que desea añadir una propiedad en es una mala idea, ya que tendrá el costo de los miembros de la propiedad para todas las instancias de esta clase.

Una solución que ofrece un mecanismo de "propiedad" cercano a lo que ofrecen otros idiomas puede estar disponible en C++ utilizando algo como lo que describo más adelante.

La única limitación que se accede a la propiedad mediante

instance.property(); // para obtener

y

instance.property() = 42; // establecer

#include <iostream> 
#include <string> 

using namespace std; 

// ------------------------------------------------------------------ 

#define PROPERTY_GET_SET(CLASS, NAME, TYPE) GetSetProperty<CLASS, TYPE> NAME() { return GetSetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME, &CLASS::set_##NAME); } 
#define PROPERTY_GET(CLASS, NAME, TYPE)  GetProperty<CLASS, TYPE> NAME() { return GetProperty<CLASS, TYPE>(this, &CLASS::get_##NAME); } 
#define PROPERTY_SET(CLASS, NAME, TYPE)  SetProperty<CLASS, TYPE> NAME() { return SetProperty<CLASS, TYPE>(this, &CLASS::set_##NAME); } 

template <typename CLASS, typename TYPE> 
struct GetSetProperty { 
    typedef TYPE (CLASS::*Getter_t)() const; 
    typedef void (CLASS::*Setter_t)(TYPE); 
    GetSetProperty(CLASS* instance, Getter_t getter, Setter_t setter) : m_instance(instance), m_getter(getter), m_setter(setter) {} 
    operator TYPE() const { return (this->m_instance->*this->m_getter)(); } 
    GetSetProperty<CLASS, TYPE>& operator=(TYPE value) { (this->m_instance->*this->m_setter)(value); return *this; } 
    CLASS* const m_instance; 
    const Getter_t m_getter; 
    const Setter_t m_setter; 
}; 

template <typename CLASS, typename TYPE> 
struct GetProperty { 
    typedef TYPE (CLASS::*Getter_t)() const; 
    GetProperty(CLASS* instance, Getter_t getter) : m_instance(instance), m_getter(getter) {} 
    operator TYPE() const { return (this->m_instance->*this->m_getter)(); } 
    CLASS* const m_instance; 
    const Getter_t m_getter; 
}; 

template <typename CLASS, typename TYPE> 
struct SetProperty { 
    typedef void (CLASS::*Setter_t)(TYPE); 
    SetProperty(CLASS* instance, Setter_t setter) : m_instance(instance), m_setter(setter) {} 
    SetProperty<CLASS, TYPE>& operator=(TYPE value) { (this->m_instance->*this->m_setter)(value); return *this; } 
    CLASS* const m_instance; 
    const Setter_t m_setter; 
}; 

template <typename CLASS, typename TYPE> 
ostream& operator<<(ostream& ostr, const GetSetProperty<CLASS, TYPE>& p) { ostr << (p.m_instance->*p.m_getter)(); return ostr; } 

template <typename CLASS, typename TYPE> 
ostream& operator<<(ostream& ostr, const GetProperty<CLASS, TYPE>& p) { ostr << (p.m_instance->*p.m_getter)(); return ostr; } 

// ------------------------------------------------------------------ 

class Dummy 
{ 
public: 

    Dummy() : m_value1(42) {} 

    PROPERTY_GET_SET(Dummy, Value1, int); 
    PROPERTY_GET_SET(Dummy, Value2, const string&); 

protected: 

    virtual int   get_Value1() const { return this->m_value1; } 
    virtual void   set_Value1(int value) { this->m_value1 = value; } 

    virtual const string& get_Value2() const { return this->m_value2; } 
    virtual void   set_Value2(const string& value) { this->m_value2 = value; } 

private: 

    int m_value1; 
    string m_value2; 
}; 


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

    Dummy d; 

    cout << d.Value1() << endl; 
    d.Value1() = 3; 
    cout << d.Value1() << endl; 

    cout << d.Value2() << endl; 
    d.Value2() = "test"; 
    cout << d.Value2() << endl; 

    return 0; 
} 

// ------------------------------------------------------------------ 
+1

Esta es una buena y válida respuesta. Me gustan especialmente los buenos ejemplos de macros. Como la programación de Macro es lo único con lo que más lucho, esto fue útil. Puede * o no ser la solución con la que voy, pero la estoy marcando como una respuesta para que los futuros lectores encuentren una buena respuesta. – lassombra