¿Las funciones get y set son populares entre los progtwigdores de C ++?

Soy del mundo de C # originalmente, y estoy aprendiendo C ++. Me he estado preguntando sobre las funciones get y set en C ++. En C # el uso de estos es bastante popular, y las herramientas como Visual Studio promueven el uso haciéndolos muy fáciles y rápidos de implementar. Sin embargo, este no parece ser el caso en el mundo C ++.

Aquí está el código C # 2.0:

public class Foo { private string bar; public string Bar { get { return bar; } set { bar = value; } } } 

O bien, en C # 3.0:

 public class Foo { get; set; } 

La gente dirá, ¿cuál es el sentido de eso? ¿Por qué no crear un campo público y luego convertirlo en una propiedad más adelante si es necesario? Honestamente, en realidad no estoy seguro. Simplemente lo hago sin buenas prácticas porque lo he visto muchas veces.

Ahora porque estoy tan acostumbrado a hacerlo, siento que debería llevar el hábito a mi código C ++, pero ¿es esto realmente necesario? No lo veo tan seguido como con C #.

De todos modos, aquí está el C ++ de lo que deduzco:

 class Foo { public: std::string GetBar() const; // Thanks for the tip Earwicker. void SetBar(std::string bar); private: std::string bar; } const std::string Foo::GetBar() { return bar; } void Foo::SetBar(std::string bar) { // Also, I always wonder if using 'this->' is good practice. this->bar = bar; } 

Ahora, para mí eso parece mucho trabajo de piernas; Considerando el uso de las herramientas de Visual Studio, la implementación de C # tomaría literalmente unos segundos en implementarse, y C ++ tardó mucho más en escribir: creo que no vale la pena el esfuerzo, especialmente cuando la alternativa es de 5 líneas:

 class Foo { public: std::string Bar; } 

Por lo que veo, estas son las ventajas:

  • Puede cambiar los detalles de implementación para las funciones get y set, por lo que en lugar de devolver un campo privado puede devolver algo más interesante.
  • Puede eliminar un get / set más tarde y hacerlo leer / escribir solamente (pero para una interfaz pública, esto parece, no es bueno).

Y las desventajas:

  • Toma años escribir, ¿ realmente vale la pena el esfuerzo? Generalmente hablando. En algunos casos, las ventajas hacen que valga la pena el esfuerzo, pero quiero decir, hablando en términos de “buenas prácticas”, ¿verdad?

Responder:

¿Por qué elegí la respuesta con menos votos ? De hecho, estaba muy cerca de elegir la respuesta de veefu ; sin embargo, mi opinión personal (que aparentemente es polémica) es que la respuesta sobre el pudín fue incitada.

La respuesta que elegí, por otro lado, parece argumentar en ambos lados; Creo que los getters y setters son malvados si se usan en exceso (con eso quiero decir, cuando no es necesario y rompería el modelo comercial), pero ¿por qué no deberíamos tener una función llamada GetBalance() ?

Seguramente esto sería mucho más versátil que PrintBalance() ; ¿Qué pasaría si quisiera mostrárselo al usuario de otra manera que la que la clase quería? Ahora bien, en cierto sentido, GetBalance() puede no ser lo suficientemente relevante como para argumentar que “los captadores y establecedores son buenos” porque no tiene (o quizás no debería ) tener un colocador acompañante, y hablando de eso, una función llamada SetBalance(float f) podría ser malo (en mi opinión) porque implicaría para el implementador de la función que la cuenta debe ser manipulada fuera de la clase, lo cual no es bueno.

Yo argumentaría que proporcionar acceso es más importante en C ++ que en C #.

C ++ no tiene soporte incorporado para las propiedades. En C # puede cambiar un campo público a una propiedad principalmente sin cambiar el código de usuario. En C ++ esto es más difícil .

Para menos tipeo puede implementar setters / getters triviales como métodos en línea:

 class Foo { public: const std::string& bar() const { return _bar; } void bar(const std::string& bar) { _bar = bar; } private: std::string _bar; }; 

Y no olvides que los captadores y setters son algo malvados.

A riesgo de ser argumentativo, apoyaré un punto de vista opuesto que encontré por primera vez cuando leía “Holub on Patterns”. Fue un punto de vista que fue muy desafiante, pero que tuvo sentido para mí al reflexionar:

Getters y Setters son malvados

El uso de getters y setters está en oposición a los fundamentos del diseño orientado a objetos: abstracción y encapsulación de datos. El uso excesivo de getters y setters hará que su código sea menos ágil y mantenible a largo plazo. En última instancia, exponen la implementación subyacente de su clase, bloqueando los detalles de implementación en la interfaz de la clase.

Imagine que su campo ‘std :: string Foo :: bar’ necesita cambiar de std :: string a otra clase de cadena, que, digamos, está mejor optimizada o admite un conjunto de caracteres diferente. Tendrá que cambiar el campo de datos privados, el getter, el setter y todo el código de cliente de esta clase que llama a estos getters y setters.

En lugar de diseñar sus clases para “proporcionar datos” y “recibir datos”, diseñe para “realizar operaciones” o “prestar servicios”. Pregúntese por qué está escribiendo una función “GetBar”. ¿Qué estás haciendo con esa información? Tal vez esté mostrando esos datos o haciendo algún procesamiento en él. ¿Este proceso está mejor expuesto como método de Foo?

Esto no quiere decir que getters y setters no tienen su propósito. En C # creo que la razón fundamental para su uso es interactuar con el IDE de Visual Studio GUI-design, pero si te encuentras escribiéndolos en C ++, es mejor dar un paso atrás, ver tu diseño y ver si algo Está perdido.

Trataré de simular un ejemplo para ilustrar.

 // A class that represents a user's bank account class Account { private: int balance_; // in cents, lets say public: const int& GetBalance() { return balance_; } void SetBalance(int b) { balance_ = b; } }; class Deposit { private: int ammount_; public: const int& GetAmount() { return ammount_; } void SetAmmount(int a) { _balance = a; } }; void DoStuffWithAccount () { Account a; // print account balance int balance = a.GetBalance(); std::cout < < balance; // deposit some money into account Deposit d(10000); a.SetBalance( a.GetBalance() + d.GetValue()); } 

No toma mucho tiempo para ver que esto está muy mal diseñado.

  1. Los enteros son un tipo de datos de moneda horrible
  2. Un depósito debe ser una función de la cuenta

Los getters y setters hacen que sea más difícil solucionar los problemas, ya que el código del cliente DoStuffWithAccount está ahora vinculado al tipo de datos que utilizamos para implementar el saldo de la cuenta.

Por lo tanto, vamos a dejar pasar este código y ver qué podemos mejorar

 // A class that represents a user's bank account class Account { private: float balance_; public: void Deposit(float b) { balance_ += b; } void Withdraw(float w) { balance_ -= w; } void DisplayDeposit(std::ostream &o) { o < < balance_; } }; void DoStuffWithAccount () { Account a; // print account balance a.DisplayBalance(std::cout); // deposit some money into account float depositAmt = 1000.00; a.Deposit(depositAmt); a.DisplayBalance(std::cout); } 

El 'flotar' es un paso en la dirección correcta. De acuerdo, podrías haber cambiado el tipo interno para 'flotar' y aun así soportar la expresión getter / setter:

 class Account { private: // int balance_; // old implementation float balance_; public: // support the old interface const int& GetBalance() { return (int) balance_; } void SetBalance(int b) { balance_ = b; } // provide a new interface for the float type const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int?? void SetBalance(float b) { balance_ = b; } }; 

pero no lleva mucho tiempo darse cuenta de que el arreglo getter / setter duplica su carga de trabajo y complica las cosas ya que necesita admitir tanto el código que usa ints como el nuevo código que usará floats. La función de depósito hace que sea un poco más fácil ampliar el rango de tipos para depositar.

Una clase tipo Cuenta probablemente no sea el mejor ejemplo, ya que "obtener" el saldo de la cuenta es una operación natural para una Cuenta. El punto general, sin embargo, es que debe tener cuidado con getters y setters. No adquiera el hábito de escribir getters y setters para cada miembro de datos. Es muy fácil exponer y encerrarse en una implementación si no tiene cuidado.

En tu ejemplo:

 class Foo { public: const std::string GetBar(); // Should this be const, not sure? 

Probablemente te refieres a esto:

 std::string GetBar() const; 

Poner el const al final significa “Esta función no modifica la instancia de Foo a la que se llama”, por lo que en cierto modo lo marca como un getter puro.

Los getters puros ocurren con frecuencia en C ++. Un ejemplo en std::ostringstream es la función str() . La biblioteca estándar a menudo sigue un patrón de usar el mismo nombre de función para un par de funciones getter / setter – str siendo un ejemplo de nuevo.

En cuanto a si es demasiado trabajo escribir y vale la pena, ¡parece una pregunta extraña! Si necesita dar acceso a los clientes a cierta información, proporcione un getter. Si no lo haces, entonces no lo hagas.

[edit] Parece que necesito enfatizar que los setters necesitan validar parámetros e imponer invariantes, por lo que generalmente no son tan simples como lo son aquí. [/editar]


No con todos, porque es el tipeo extra. Tiendo a usarlos mucho más a menudo ahora que Visual Assist me da “encapsulate field”.

El trabajo de campo no es más si implementa solo los setters / getters predeterminados en línea en la statement de clase (lo que tiendo a hacer, sin embargo, los setters más complejos se mueven hacia el cuerpo).

Algunas notas:

constness: Sí, el getter debería ser const. Sin embargo, no sirve de nada restablecer el valor de retorno si regresas por valor. Para valores de retorno potencialmente complejos, es posible que desee usar const y though:

 std::string const & GetBar() const { return bar; } 

Encadenamiento de setter: a muchos desarrolladores les gusta modificar el setter como tal:

 Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; } 

Lo cual permite llamar a múltiples setters como tales:

 Foo foo; foo.SetBar("Hello").SetBaz("world!"); 

Sin embargo, no es universalmente aceptado como algo bueno.

__declspec (propiedad) : Visual C ++ proporciona esta extensión no estándar para que las personas que llaman puedan volver a utilizar la syntax de la propiedad. Esto aumenta un poco el trabajo de campo en la clase, pero hace que el código de la persona que llama sea mucho más amigable.


Entonces, en conclusión, hay un poco más de trabajo de campo, pero un puñado de decisiones para hacer en C ++. Típico;)

No hay una convención realmente estricta sobre esto, como en C # o Java. Muchos progtwigdores de C ++ simplemente harían pública la variable y se ahorrarían el problema.

Como han dicho otras respuestas, a menudo no se necesita configurar, y hasta cierto punto, obtener métodos.

Pero si los hace, no es necesario escribir más de lo necesario:

 class Foo { public: std::string Bar() const { return bar; } void Bar(const std::string& bar) { this->bar = bar; } private: std::string bar; }; 

Declarar las funciones en línea en la clase guarda el tipeo y le indica al comstackdor que desea que las funciones estén en línea. Y no es mucho más tipeo que los equivalentes de C #. Una cosa a tener en cuenta es que eliminé los prefijos get / set. En cambio, solo tenemos dos sobrecargas Bar (). Eso es bastante común en C ++ (después de todo, si no toma ningún argumento, sabemos que es el captador, y si toma un argumento, es el colocador. No necesitamos el nombre para decirnos eso), y ahorra un poco más de tipeo

Casi nunca uso getters y setters en mi propio código. La respuesta de Veefu me parece bien.

Si insiste en tener getters y / o setters, puede usar macros para reducir la placa de la caldera.

 #define GETTER(T,member) const T& Get##member() const { return member; } #define SETTER(T,member) void Set##member(const T & value) { member = value; } class Foo { public: GETTER(std::string, bar) SETTER(std::string, bar) private: std::string bar; } 

Obteniendo y configurando miembros de datos como miembros de datos: Malos .
Obtener y establecer elementos de la abstracción: bueno .

Los argumentos en contra de Get / Set en términos de diseño de API en el ejemplo de banca son acertados. No exponga los campos o propiedades si permiten a los usuarios romper sus reglas comerciales.

Sin embargo, una vez que haya decidido que necesita un campo o propiedad, siempre use una propiedad.

Las propiedades automáticas en c # son muy fáciles de usar, y hay muchos escenarios (enlace de datos, serialización, etc.) que no funcionan con campos, pero requieren propiedades.

Si está desarrollando componentes COM entonces sí, es muy popular.

obtener y establecer son un dolor infligido a la gente si tiene que usarlos en cualquier idioma.

Eiffel tiene mucho mejor donde todo lo que difiere es la cantidad de información que debe proporcionar para obtener la respuesta: una función con 0 parms equivale a acceder a una variable miembro, y puede cambiar libremente entre ellos.

Cuando controlas ambos lados de una interfaz, la definición de la interfaz no parece ser un gran problema. Sin embargo, cuando quiera cambiar los detalles de implementación e inflige la recomstackción del código del cliente como es el caso común en C ++, desea poder minimizar esto tanto como sea posible. Como tal, pImpl y get / set se usarían más en las API públicas para evitar dicho daño.

Los métodos Get y Set son útiles si tiene restricciones en un valor de variable. Por ejemplo, en muchos modelos matemáticos hay una restricción para mantener una cierta variable de flotación en el rango [0,1]. En este caso, Get y Set (especialmente Set) pueden jugar un buen rol:

 class Foo{ public: float bar() const { return _bar; } void bar(const float& new_bar) { _bar = ((new_bar < = 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1] private: float _bar; // must be in range [0,1] }; 

Además, algunas propiedades deben ser recalculadas antes de leer. En esos casos, puede tomar mucho tiempo de cálculo innecesario para volver a calcular cada ciclo. Por lo tanto, una forma de optimizarlo es volver a calcular solo al leer en su lugar. Para hacerlo, sobrecargue el método Get para actualizar la variable antes de leerla.

De lo contrario, si no es necesario validar los valores de entrada o actualizar los valores de salida, hacer que la propiedad sea pública no es un delito y usted puede aceptarla.

Si utiliza C ++ / CLI como su variante de C ++, entonces tiene soporte de propiedad nativa en el idioma, por lo que puede usar

 property String^ Name; 

Esto es lo mismo que

 String Name{get;set;} 

Cª#. Si necesita un control más preciso sobre los métodos get / set, puede usar

 property String^ Name { String^ get(); void set(String^ newName); } 

en el encabezado y

 String^ ClassName::Name::get() { return m_name; } void ClassName::Name::set(String^ newName) { m_name = newName; } 

en el archivo .cpp. No recuerdo nada, pero creo que puede tener permisos de acceso diferentes para los métodos get y set (público / privado, etc.).

Colin

Sí, get y set son populares en el mundo c ++.

El comstackdor emitirá set_ y get_ si define una propiedad, por lo que en realidad es solo guardar algo de tipeo.

Esta ha sido una discusión interesante. Esto es algo de mi libro favorito “CLR via C #”.

Esto es lo que cité.

Personalmente, no me gustan las propiedades y me gustaría que no fueran compatibles con Microsoftm.NET Framework y sus lenguajes de progtwigción. La razón es porque las propiedades parecen campos pero son métodos. Se sabe que esto causa una cantidad fenomenal de confusiones. Cuando un progtwigdor ve código que parece estar accediendo a un campo, hay muchas suposiciones que hace el progtwigdor que pueden no ser ciertas para una propiedad. Por ejemplo,

  • Una propiedad puede ser de solo lectura o solo escritura; el acceso al campo es siempre
    legible y escribible. Si defines
    una propiedad, lo mejor es ofrecer ambos
    obtener y establecer métodos de acceso.
  • Un método de propiedad puede arrojar una excepción; el acceso al campo nunca arroja
    una excepción.

  • Una propiedad no se puede pasar como un parámetro de salida o ref a un método; un campo puede.

  • Un método de propiedad puede llevar mucho tiempo para ejecutarse; acceso de campo siempre
    finaliza de inmediato. Una común
    razón para usar propiedades es
    realizar la sincronización del hilo, que puede detener el hilo para siempre, y
    por lo tanto, una propiedad no debería ser
    usado si la sincronización de hilo es
    necesario. En esa situación, se prefiere un método. Además, si se puede acceder a su clase de forma remota (por ejemplo,
    tu clase se deriva de
    System.MashalByRefObject), llamando
    el método de propiedad será muy
    lento, y por lo tanto, un método es
    preferido a una propiedad. En mi
    opinión, clases derivadas de
    MarshalByRefObject nunca debe usar
    propiedades.

  • Si se llama varias veces seguidas, puede regresar un método de propiedad
    un valor diferente cada vez; un
    campo devuelve el mismo valor cada
    hora. La clase System.DateTime tiene una propiedad de solo lectura ahora que devuelve
    la fecha y hora actual. Cada vez que consulta esta propiedad, lo hará
    devolver un valor diferente Esto es un
    error, y Microsoft desea que
    podrían arreglar la clase haciendo
    Ahora es un método en lugar de una propiedad.

  • Un método de propiedad puede causar efectos secundarios observables; el acceso al campo nunca lo hace. En otras palabras, un usuario de un tipo debería ser capaz de establecer varios
    propiedades definidas por un tipo en cualquier
    orden que él o ella elija sin
    notando cualquier comportamiento diferente en
    el tipo.

  • Un método de propiedad puede requerir memoria adicional o devolver una
    referencia a algo que no es
    en realidad parte del estado del objeto, por lo que la modificación del objeto devuelto tiene
    no tiene efecto en el objeto original;
    consultar un campo siempre devuelve un
    referencia a un objeto que es
    garantizado para ser parte del estado del objeto original. Trabajando con un
    propiedad que devuelve una copia puede ser
    muy confuso para los desarrolladores, y
    esta característica a menudo no está documentada.