2010-04-01 10 views
11

¿Cómo tengo propiedades en la clase C++, como lo tiene en una clase C#.Tener propiedades públicas en la clase C++

No quiero tener métodos getter y setter.

+2

Eche un vistazo a una pregunta similar aquí: http://stackoverflow.com/questions/2017623/forward-unbreakable-accessor-class-templates-c. Discutimos algunas formas de implementar propiedades usando las características de C++. –

+0

¿C++ específico de plataforma o independiente de plataforma? En Windows, el compilador de VC++ tiene extensiones no estándar que admiten esto. Ver mi respuesta a continuación. –

Respuesta

11

Puede utilizar una solución similar a la sugerida por Jon, pero manteniendo la semántica ordinaria de C++ utilizando la sobrecarga del operador. He modificado ligeramente código de Jon de la siguiente manera (explicaciones siguen el código):

#include <iostream> 

template<typename T> 
class Accessor { 
public: 
    explicit Accessor(const T& data) : value(data) {} 

    Accessor& operator=(const T& data) { value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { this->value = other.value; return *this; } 
    operator T() const { return value; } 
    operator T&() { return value; } 

private: 
    Accessor(const Accessor&); 


    T value; 

}; 

struct Point { 
    Point(int a = 0, int b = 0) : x(a), y(b) {} 
    Accessor<int> x; 
    Accessor<int> y; 
}; 

int main() { 
    Point p; 
    p.x = 10; 
    p.y = 20; 
    p.x++; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

sobrecargamos operator= para retener la sintaxis de asignación habitual en lugar de una sintaxis de la función de llamada similares. Usamos el operador de reparto como un "getter". Necesitamos la segunda versión del operator= para permitir la asignación del segundo tipo en main().

Ahora puede agregar a los punteros de la función de constructor de Accessor, o mejor - functors - para llamar como getters/setters de cualquier manera que le parezca adecuada. En el siguiente ejemplo se supone que el colocador bool retorno de la función de transmitir acuerdo para establecer el nuevo valor, y el comprador solo puede modificarlo en la manera de salir:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) 
    { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator()(const T& data) 
    { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { value = getter(value); return value;} 
    operator T&() { value = getter(value); return value; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

Sin embargo, como la última línea demuestra que tiene un error. El operador de reparto que devuelve un T & permite a los usuarios eludir el establecimiento, ya que les da acceso al valor privado. Una forma de resolver este error es implementar todos los operadores que desee que proporcione su Accesor.Por ejemplo, en el siguiente código que utiliza el operador + =, y ya que me quita la referencia operador de conversión de regresar tuve que poner en práctica un operator+=:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) const { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator() (const T& data) const { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) const { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) const { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
private: 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { return getter(value);} 

    Accessor& operator+=(const T& data) { if (setter(value+data)) value += data; return *this; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

Vas a tener que implementa todos los operadores que vas usar.

+0

+1 por darme una paliza, pero no me gusta la idea de proporcionar toneladas de sobrecargas de operador como esa. Otra forma de cuidar valores posiblemente invalidados externamente es ofrecer una devolución de llamada "fue escrita" diferente que se invoca cuando se lee el valor siguiente. –

+0

Considerando que solo define a todos esos operadores una vez en la clase de plantilla, no creo que sea tan terrible. El código STL o boost es horrible comparado con esto (y en general también :)) en mi opinión. Pero también hay otras opciones. Uno debe considerar las circunstancias de uno y hacer su elección en consecuencia. :) – conio

1

No es así. C++ no es compatible con propiedades como C#. Si desea que el código se ejecute en set/get, tendrá que ser un método.

+0

Las propiedades realmente también ejecutan métodos en C#. El compilador lo oculta de ti. –

7

Para un comportamiento como este, utilizo un meta-acceso de plantilla. Aquí hay una muy simplificado para este tipo POD:

template<class T> 
struct accessor { 

    explicit accessor(const T& data) : value(data) {} 
    T operator()() const { return value; } 
    T& operator()() { return value; } 
    void operator()(const T& data) { value = data; } 

private: 

    accessor(const accessor&); 
    accessor& operator=(const accessor&); 
    T value; 

}; 

El uso típico es la siguiente:

struct point { 
    point(int a = 0, int b = 0) : x(a), y(b) {} 
    accessor<int> x; 
    accessor<int> y; 
}; 

point p; 
p.x(10); 
p.y(20); 
p.x()++; 
std::cout << p.x(); 

El compilador normalmente inlines estas llamadas si establecer las cosas bien y tener la optimización activada. No es más un cuello de botella de rendimiento que el uso de getters y setters reales, sin importar las optimizaciones que sucedan. Es trivial extender esto para admitir automáticamente tipos que no sean POD o enumerados, o para permitir el registro de devoluciones de llamadas cuando se leen o escriben datos.

Editar: Si prefiere no utilizar los paréntesis, siempre puede definir operator=() y un operador de conversión implícito. Aquí hay una versión que hace justamente eso, al tiempo que agrega soporte básico de devolución de llamada "cosas sucedidas":

Editar más: Bien, me extrañó que alguien ya haya hecho una versión revisada de mi código. Suspiro.

+0

¿Por qué usa un estilo de funtor torpe cuando podría haber definido operator =? –

+0

@Ben: se agregaron diferencias por completarse. Me pasó por la cabeza cómo se ve esa mierda en C#. –

1

Las propiedades no están soportados en C++, pero se puede ponerlas en práctica:
1) Mediante el uso de plantillas
2) haciendo extensión del lenguaje y la escritura de código personalizado preprocesador

Cualquiera de estos enfoques no será fácil, Pero puede hacerse.

+0

C++ admite sobrecarga operator =, por lo que no se requieren extensiones. –

+0

@Ben Voigt Esto depende de qué exactamente desea implementar y cuántas propiedades desea. Con una gran cantidad de código, introducir una palabra clave o dos y escribir el preprocesador de código será una mejor idea: hará que el código sea más legible. – SigTerm

3

Aquí hay una implementación de PoC que hice hace un tiempo, funciona muy bien, excepto que necesita configurar algo en el constructor para que funcione bien y sin problemas.

http://www.codef00.com/code/Property.h

Aquí está el ejemplo de uso:

#include <iostream> 
#include "Property.h" 


class TestClass { 
public: 
    // make sure to initialize the properties with pointers to the object 
    // which owns the property 
    TestClass() : m_Prop1(0), m_Prop3(0.5), prop1(this), prop2(this), prop3(this) { 
    } 

private: 
    int getProp1() const { 
     return m_Prop1; 
    } 

    void setProp1(int value) { 
     m_Prop1 = value; 
    } 

    int getProp2() const { 
     return 1234; 
    } 

    void setProp3(double value) { 
     m_Prop3 = value; 
    } 

    int m_Prop1; 
    double m_Prop3; 

public: 
    PropertyRW<int, TestClass, &TestClass::getProp1, &TestClass::setProp1> prop1; 
    PropertyRO<int, TestClass, &TestClass::getProp2> prop2; 
    PropertyWO<double, TestClass, &TestClass::setProp3> prop3; 
}; 

y un cierto uso de esta clase ...

int main() { 
    unsigned int a; 
    TestClass t; 
    t.prop1 = 10; 
    a = t.prop1; 
    t.prop3 = 5; 
    a = t.prop2; 
    std::cout << a << std::endl; 
    return 0; 
} 

Hay dos molestias con este enfoque:

  1. Tienes que g Ive la propiedad un puntero a su clase propietaria.
  2. La sintaxis para declarar una propiedad es una poco prolijo, pero apuesto a que puedo limpiar que un poco con algunas macros
+0

Un poco incómodo, sí, pero la idea de registrar el descriptor de acceso con 'this' es buena, ya que permite fácilmente que las propiedades jueguen trucos con quién puede acceder a ellas basándose en la instancia y tipo de información. –

1

podría proporcionar métodos get y set que tienen nombres similares a los miembros de datos :

class Example 
{ 
    private: 
    unsigned int x_; 
    double d_; 
    std::string s_s; 
    public: 
    unsigned int x(void) const 
    { return x_;} 

    void x(unsigned int new_value) 
    { x_ = new_value;} 

    double d(void) const 
    { return d_;} 
    void d(double new_value) 
    { d_ = new_value;} 

    const std::string& s(void) const 
    { return s_;} 
    void s(const std::string& new_value) 
    { s_ = new_value;} 
}; 

Aunque esto se acerca, ya que requiere el uso de '()' para cada miembro, que no cumple con la funcionalidad exacta de propiedades que Microsoft lenguajes proporcionan.

La coincidencia más cercana para las propiedades es declarar los miembros de datos como públicos.

+0

Incorrecto, al sobrecargar operador = puede obtener la sintaxis exacta (para el usuario) y las capacidades de cualquier idioma que proporcione propiedades para usted. –

3

Si no le importa que su código C++ no se compile con nada que no sea el compilador de Microsoft Visual C++, puede usar algunas de las extensiones no estándar del compilador.

Por ejemplo, el siguiente código creará una propiedad similar a C# llamada MyProperty.

struct MyType 
{ 
    // This function pair may be private (for clean encapsulation) 
    int get_number() const { return m_number; } 
    void set_number(int number) { m_number = number; } 

    __declspec(property(get=get_number, put=set_number)) int MyProperty; 
private: 
    int m_number: 
} 

int main() 
{ 
    MyType m; 
    m.MyProperty = 100; 
    return m.MyProperty; 
} 

Más información sobre esta extensión del lenguaje específico de Microsoft está disponible here.

Cuestiones relacionadas