¿C ++ 11 tiene propiedades de estilo C #?


93

En C #, hay un buen azúcar de sintaxis para campos con getter y setter. Además, me gustan las propiedades implementadas automáticamente que me permiten escribir

public Foo foo { get; private set; }

En C ++ tengo que escribir

private:
    Foo foo;
public:
    Foo getFoo() { return foo; }

¿Existe algún concepto de este tipo en C ++ 11 que me permita tener algo de sintaxis en esto?


62
Podría hacerse con un par de macros. huye avergonzado
Mankarse

7
@Eloff: Hacer público todo es SIEMPRE una mala idea.
Kaiserludi

8
¡No existe tal concepto! Y no lo necesitas también: seanmiddleditch.com/why-c-does-not-need-c-like-properties
CinCout

2
a) esta pregunta es bastante antigua b) estaba pidiendo azúcar sintáctica, que me permitiría deshacerme de los paréntesis c) aunque el artículo presenta argumentos válidos contra la adaptación de propiedades, ya sea que C ++ 'necesita o no' propiedades es muy subjetivo. C ++ es equivalente a Touring-machine incluso sin ellos, pero eso no significa que tener tal azúcar de sintaxis haría que C ++ sea más productivo.
Radim Vansa

3
Definitivamente no.

Respuestas:


87

En C ++ puedes escribir tus propias características. Aquí hay una implementación de ejemplo de propiedades usando clases sin nombre. Artículo de Wikipedia

struct Foo
{
    class {
        int value;
        public:
            int & operator = (const int &i) { return value = i; }
            operator int () const { return value; }
    } alpha;

    class {
        float value;
        public:
            float & operator = (const float &f) { return value = f; }
            operator float () const { return value; }
    } bravo;
};

Puede escribir sus propios captadores y definidores en su lugar y si desea acceso de miembros de la clase titular, puede extender este código de ejemplo.


1
¿Alguna idea sobre cómo modificar este código para tener una variable miembro privada a la que Foo pueda acceder internamente, mientras que la API pública solo expone la propiedad? Por supuesto, podría hacer a Foo un amigo de alpha / beta, pero aún así tendría que escribir alpha.value para acceder al valor, pero preferiría que acceder directamente a la variable miembro desde el interior de Foo se sintiera más como acceder a un miembro de Foo y no a un miembro de una clase de propiedad anidada especial.
Kaiserludi

1
@Kaiserludi Sí: en este caso, haga que alpha y bravo sean privados. En Foo, puede leer / escribir con esas "propiedades" anteriores, pero fuera de Foo, esto ya no sería posible. Para eludir eso, haga una referencia constante que sea pública. Es accesible desde el exterior, pero solo para lectura ya que es una referencia constante. La única advertencia es que necesitará otro nombre para la referencia const pública. Yo personalmente usaría _alphapara la variable privada y alphapara la referencia.
Kapichu

1
@Kapichu: no es una solución, por 2 razones. 1) En C #, los captadores / definidores de propiedades se utilizan a menudo para incrustar comprobaciones de seguridad que se obligan a los usuarios públicos de la clase al tiempo que permiten que las funciones miembro accedan al valor directamente. 2) las referencias const no son gratuitas: dependiendo del compilador / plataforma se ampliarán sizeof(Foo).
ceztko

@psx: debido a las limitaciones de este enfoque, lo evitaría y esperaría la adición adecuada al estándar, si es que alguna vez aparece.
ceztko

@Kapichu: Pero alpha y bravo en el código de ejemplo son las propiedades. Me gustaría acceder directamente a la variable en sí desde el interior de la implementación de Foo, sin la necesidad de usar una propiedad, mientras que solo me gustaría exponer el acceso a través de una propiedad en la API.
Kaiserludi

53

C ++ no tiene esto integrado, puede definir una plantilla para imitar la funcionalidad de las propiedades:

template <typename T>
class Property {
public:
    virtual ~Property() {}  //C++11: use override and =default;
    virtual T& operator= (const T& f) { return value = f; }
    virtual const T& operator() () const { return value; }
    virtual explicit operator const T& () const { return value; }
    virtual T* operator->() { return &value; }
protected:
    T value;
};

Para definir una propiedad :

Property<float> x;

Para implementar un getter / setter personalizado, simplemente herede:

class : public Property<float> {
    virtual float & operator = (const float &f) { /*custom code*/ return value = f; }
    virtual operator float const & () const { /*custom code*/ return value; }
} y;

Para definir una propiedad de solo lectura :

template <typename T>
class ReadOnlyProperty {
public:
    virtual ~ReadOnlyProperty() {}
    virtual operator T const & () const { return value; }
protected:
    T value;
};

Y para usarlo en claseOwner :

class Owner {
public:
    class : public ReadOnlyProperty<float> { friend class Owner; } x;
    Owner() { x.value = 8; }
};

Puede definir algunos de los anteriores en macros para hacerlo más conciso.


Tengo curiosidad por saber si esto se compila a una característica de costo cero, no sé si empaquetar cada miembro de datos en una instancia de clase dará como resultado el mismo tipo de empaquetado de estructura, por ejemplo.
Dai

1
La lógica de "getter / setter personalizado" podría hacerse sintácticamente más limpia mediante el uso de funciones lambda, desafortunadamente no puede definir una lambda fuera de un contexto ejecutable en C ++ (¡todavía!) Así que sin usar una macro de preprocesador terminará con un código que es solo tan complicado como los getters / setters tontos, lo cual es desafortunado.
Dai

2
La "clase: ..." en el último ejemplo es interesante y falta en los otros ejemplos. Crea la declaración de amistad necesaria, sin introducir un nuevo nombre de clase.
Hans Olsson

Una gran diferencia entre esto y la respuesta del 19 de noviembre de 2010 es que permite anular el captador o el definidor caso por caso. De esa manera, uno podría examinar la entrada para que esté dentro del rango, o enviar una notificación de cambio para cambiar los oyentes de eventos, o un lugar para colgar un punto de interrupción.
Eljay

Tenga en cuenta que el virtuales probablemente innecesario para la mayoría de los casos de uso, ya que es poco probable que sea polimórfica uso de una propiedad.
Eljay

28

No hay nada en el lenguaje C ++ que funcione en todas las plataformas y compiladores.

Pero si está dispuesto a romper la compatibilidad multiplataforma y comprometerse con un compilador específico, es posible que pueda usar dicha sintaxis, por ejemplo, en Microsoft Visual C ++ , puede hacerlo

// declspec_property.cpp  
struct S {  
   int i;  
   void putprop(int j) {   
      i = j;  
   }  

   int getprop() {  
      return i;  
   }  

   __declspec(property(get = getprop, put = putprop)) int the_prop;  
};  

int main() {  
   S s;  
   s.the_prop = 5;  
   return s.the_prop;  
}

2
Esto también funciona con clang
Passer By

18

Puede emular getter y setter hasta cierto punto si tiene un miembro de tipo dedicado y anulando operator(type)y operator=para él. Si es una buena idea es otra pregunta y voy a +1la respuesta de Kerrek SB para expresar mi opinión al respecto :)


Puede emular la llamada a un método al asignar o leer desde ese tipo, pero no puede distinguir quién llama a la operación de asignación (para prohibirla si no es el propietario del campo): lo que estoy tratando de hacer al especificar un acceso diferente nivel para getter y setter.
Radim Vansa

@Flavius: Simplemente agregue un friendal propietario del campo.
kennytm

17

Quizás eche un vistazo a la clase de propiedad que he reunido durante las últimas horas: /codereview/7786/c11-feedback-on-my-approach-to-c-like-class-properties

Te permite tener propiedades que se comporten así:

CTestClass myClass = CTestClass();

myClass.AspectRatio = 1.4;
myClass.Left = 20;
myClass.Right = 80;
myClass.AspectRatio = myClass.AspectRatio * (myClass.Right - myClass.Left);

Agradable, sin embargo, aunque esto permite accesos definidos por el usuario, no existe la función de captador público / configurador privado que estaba buscando.
Radim Vansa

17

Con C ++ 11 puede definir una plantilla de clase de propiedad y usarla así:

class Test{
public:
  Property<int, Test> Number{this,&Test::setNumber,&Test::getNumber};

private:
  int itsNumber;

  void setNumber(int theNumber)
    { itsNumber = theNumber; }

  int getNumber() const
    { return itsNumber; }
};

Y aquí está la plantilla de la clase Propiedad.

template<typename T, typename C>
class Property{
public:
  using SetterType = void (C::*)(T);
  using GetterType = T (C::*)() const;

  Property(C* theObject, SetterType theSetter, GetterType theGetter)
   :itsObject(theObject),
    itsSetter(theSetter),
    itsGetter(theGetter)
    { }

  operator T() const
    { return (itsObject->*itsGetter)(); }

  C& operator = (T theValue) {
    (itsObject->*itsSetter)(theValue);
    return *itsObject;
  }

private:
  C* const itsObject;
  SetterType const itsSetter;
  GetterType const itsGetter;
};

2
que C::*significa ¿Nunca había visto algo así antes?
Rika

1
Es un puntero a una función miembro no estática en la clase C. Esto es similar a un puntero de función simple, pero para llamar a la función miembro, debe proporcionar un objeto en el que se llame a la función. Esto se logra con la línea itsObject->*itsSetter(theValue)del ejemplo anterior. Consulte aquí para obtener una descripción más detallada de esta función.
Christoph Böhme

@Niceman, ¿hay algún ejemplo de uso? Parece ser muy costoso tenerlo como miembro. Tampoco es particularmente útil como miembro estático.
Grim Fandango

16

Como muchos otros ya han dicho, no hay soporte integrado en el idioma. Sin embargo, si su objetivo es el compilador de Microsoft C ++, puede aprovechar la extensión específica de Microsoft para las propiedades que se documenta aquí.

Este es el ejemplo de la página vinculada:

// declspec_property.cpp
struct S {
   int i;
   void putprop(int j) { 
      i = j;
   }

   int getprop() {
      return i;
   }

   __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main() {
   S s;
   s.the_prop = 5;
   return s.the_prop;
}

12

No, C ++ no tiene concepto de propiedades. Aunque puede ser incómodo definir y llamar a getThis () o setThat (valor), está haciendo una declaración al consumidor de esos métodos de que puede ocurrir alguna funcionalidad. Acceder a campos en C ++, por otro lado, le dice al consumidor que no ocurrirá ninguna funcionalidad adicional o inesperada. Las propiedades harían esto menos obvio ya que el acceso a la propiedad a primera vista parece reaccionar como un campo, pero de hecho reacciona como un método.

Aparte, estaba trabajando en una aplicación .NET (un CMS muy conocido) intentando crear un sistema de membresía de cliente. Debido a la forma en que habían usado las propiedades para sus objetos de usuario, se dispararon acciones que no había anticipado, lo que provocó que mis implementaciones se ejecutaran de formas extrañas, incluida la recursividad infinita. Esto se debía a que sus objetos de usuario realizaban llamadas a la capa de acceso a datos o algún sistema de almacenamiento en caché global cuando intentaban acceder a cosas simples como StreetAddress. Todo su sistema se basó en lo que yo llamaría un abuso de propiedad. Si hubieran usado métodos en lugar de propiedades, creo que me habría dado cuenta de lo que estaba fallando mucho más rápidamente. Si hubieran usado campos (o al menos hubieran hecho que sus propiedades se comportaran más como campos), creo que el sistema hubiera sido más fácil de extender y mantener.

[Editar] Cambió mis pensamientos. Había tenido un mal día y fui un poco enfadado. Esta limpieza debería ser más profesional.


11

Basado en https://stackoverflow.com/a/23109533/404734, aquí hay una versión con un captador público y un configurador privado:

struct Foo
{
    class
    {
            int value;
            int& operator= (const int& i) { return value = i; }
            friend struct Foo;
        public:
            operator int() const { return value; }
    } alpha;
};

4

Esto no es exactamente una propiedad, pero hace lo que quiere de la manera más sencilla:

class Foo {
  int x;
public:
  const int& X;
  Foo() : X(x) {
    ...
  }
};

Aquí la gran X se comporta como public int X { get; private set; }en la sintaxis de C #. Si desea propiedades completas, hice un primer intento para implementarlas aquí .


2
No es buena idea. Siempre que haga una copia de un objeto de esta clase, la referencia Xdel nuevo objeto seguirá apuntando al miembro del objeto antiguo, porque simplemente se copia como un miembro puntero. Esto es malo en sí mismo, pero cuando se elimina el objeto antiguo, se daña la memoria. Para que esto funcione, también tendría que implementar su propio constructor de copia, operador de asignación y constructor de movimiento.
toster

4

Probablemente lo sepas, pero yo simplemente haría lo siguiente:

class Person {
public:
    std::string name() {
        return _name;
    }
    void name(std::string value) {
        _name = value;
    }
private:
    std::string _name;
};

¡Este enfoque es simple, no utiliza trucos ingeniosos y hace el trabajo!

Sin embargo, el problema es que a algunas personas no les gusta prefijar sus campos privados con un guión bajo y, por lo tanto, realmente no pueden usar este enfoque, pero afortunadamente para quienes lo hacen, es realmente sencillo. :)

Los prefijos get y set no agregan claridad a su API, pero los hacen más detallados y la razón por la que no creo que agreguen información útil es porque cuando alguien necesita usar una API si la API tiene sentido, probablemente se dará cuenta de qué prescinde de los prefijos.

Una cosa más, es fácil comprender que se trata de propiedades porque nameno es un verbo.

En el peor de los casos, si las API son coherentes y la persona no se dio cuenta de que name()es un acceso yname(value) es un mutador, solo tendrá que buscarlo una vez en la documentación para comprender el patrón.

Por mucho que me guste C #, ¡no creo que C ++ necesite propiedades en absoluto!


Sus mutadores tienen sentido si uno usa foo(bar)(en lugar de los más lentos foo = bar), pero sus accesos no tienen nada que ver con las propiedades ...
Matthias

@Matthias Al decir que no tiene nada que ver con propiedades en absoluto, no me dice nada, ¿puedes darme más detalles? además no intenté compararlos, pero si necesitas un mutador y un acceso, puedes usar esta convención.
Eyal Solnik

La pregunta es sobre el concepto de software de Propiedades. Las propiedades se pueden usar como si fueran miembros de datos públicos (uso), pero en realidad son métodos especiales llamados descriptores de acceso (declaración). Su solución se adhiere a los métodos (getters / setters ordinarios) para la declaración y el uso. Así que, en primer lugar, definitivamente no es el uso que pide el OP, sino una convención de nomenclatura extraña y poco convencional (y, por lo tanto, en segundo lugar, tampoco azúcar sintáctico).
Matthias

Como efecto secundario menor, su mutador funciona sorprendentemente como una propiedad, ya que en C ++ uno se inicializa mejor con en foo(bar)lugar de lo foo=barque se puede lograr con un void foo(Bar bar)método de mutador en una _foovariable miembro.
Matthias

@Matthias Sé qué son las propiedades, he estado escribiendo C ++ bastante y C # durante más de una década, no estoy discutiendo sobre el beneficio de las propiedades y lo que son, pero nunca REALMENTE las necesité en C ++, de hecho tú Estoy diciendo que se pueden usar como datos públicos y eso es mayormente cierto, pero hay casos en C # en los que ni siquiera se pueden usar propiedades directamente, como pasar una propiedad por referencia mientras que con un campo público, sí se puede.
Eyal Solnik

4

No, pero debería considerar si se trata de una función get: set y no se realiza ninguna tarea adicional dentro de los métodos get: set, simplemente hágalo público.


2

Recopilé las ideas de múltiples fuentes de C ++ y las puse en un ejemplo agradable, todavía bastante simple para getters / setters en C ++:

class Canvas { public:
    void resize() {
        cout << "resize to " << width << " " << height << endl;
    }

    Canvas(int w, int h) : width(*this), height(*this) {
        cout << "new canvas " << w << " " << h << endl;
        width.value = w;
        height.value = h;
    }

    class Width { public:
        Canvas& canvas;
        int value;
        Width(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } width;

    class Height { public:
        Canvas& canvas;
        int value;
        Height(Canvas& canvas): canvas(canvas) {}
        int & operator = (const int &i) {
            value = i;
            canvas.resize();
            return value;
        }
        operator int () const {
            return value;
        }
    } height;
};

int main() {
    Canvas canvas(256, 256);
    canvas.width = 128;
    canvas.height = 64;
}

Salida:

new canvas 256 256
resize to 128 256
resize to 128 64

Puede probarlo en línea aquí: http://codepad.org/zosxqjTX


Hay una sobrecarga de memoria para mantener autorreferencias, + una sintaxis torpe de ctor.
Red.Wave

¿Proponer propiedades? Supongo que ha habido un montón de propuestas de este tipo rechazadas.
Red.Wave

@ Red.Wave Inclínate ante el señor y maestro del rechazo entonces. Bienvenido a C ++. Clang y MSVC tienen extensiones personalizadas para las propiedades, si no desea las autorreferencias.
lama12345

Nunca podré inclinarme. Creo que no todas las funciones son adecuadas. Para mí, los objetos son mucho más que un par de funciones setter + getter. He probado implementaciones propias evitando la sobrecarga de memoria permanente innecesaria, pero la sintaxis y la tarea de declarar instancias no fue satisfactoria; Me atrajo el uso de macros declarativas, pero de todos modos no soy un gran fanático de las macros. Y mi enfoque finalmente condujo a propiedades a las que se accede con sintaxis de función, que muchos, incluido yo mismo, no aprobamos.
Red.Wave

0

¿Su clase realmente necesita hacer cumplir algún invariante o es solo una agrupación lógica de elementos miembros? Si es lo último, debería considerar convertir la cosa en una estructura y acceder a los miembros directamente.


0

Hay un conjunto de macros escrito aquí . Esto tiene declaraciones de propiedad convenientes para tipos de valor, tipos de referencia, tipos de solo lectura, tipos fuertes y débiles.

class MyClass {

 // Use assign for value types.
 NTPropertyAssign(int, StudentId)

 public:
 ...

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.