Pregunta

Tengo una clase simple contenedor con un constructor de copia.

¿Usted recomienda el uso de captadores y definidores, o acceder a las variables miembro directamente?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • En el ejemplo, todo el código está en línea, pero en nuestro código real no hay código en línea.

Actualizar (29 Sept 09):

Algunas de estas respuestas están bien escritos, sin embargo parece que de falta el punto de esta pregunta:

  • esto es ejemplo artificial sencillo de discutir el uso de getters / setters vs las variables

  • listas de inicializador o funciones de validación privados no son realmente parte de esta pregunta. Me pregunto si alguno de diseño hará que el código sea más fácil de mantener y ampliar.

  • Algunos PPL se están centrando en la cadena en este ejemplo sin embargo, es sólo un ejemplo, imagina que es un objeto diferente en su lugar.

  • No estoy preocupado por el rendimiento. no estamos programando en el PDP-11

¿Fue útil?

Solución

¿Se prevé cómo se devuelve la cadena, por ejemplo. espacio en blanco recortado, nula marcado, etc.? Lo mismo con SetMyString (), si la respuesta es sí, usted está mejor con métodos de acceso, ya que no tiene que cambiar su código en lugares trillón pero basta con modificar los métodos get y set.

Otros consejos

EDIT: Responder a la pregunta editado:)

  

esto es simple ejemplo artificial a    discutir el uso de getters / setters vs    Variables

Si usted tiene una colección de variables simples, que no necesita ningún tipo de validación, ni procesamiento adicional entonces es posible considerar el uso de un POD en su lugar. De de BS FAQ :

  

Una clase bien diseñado presenta una limpia   y una interfaz sencilla a sus usuarios,    ocultar su representación y el ahorro   sus usuarios de tener que saber acerca de   que la representación. Si el   representación no debe estar oculto -   decir, ya que los usuarios deben ser capaces de   cambiar cualquier miembro de datos de la forma que   Al igual que - se puede pensar en esa clase como   "Sólo una estructura de datos simple y llano"

En resumen, esto no es Java. No se debe escribir lisos getters / setters porque son tan malas como la exposición de las variables de ellos mismos.

  

listas de inicializador o funciones de validación privados no son realmente   parte de esta pregunta. me pregunto   Si bien el diseño hará que el código   más fácil de mantener y ampliar.

Si va a copiar las variables de otro objeto, entonces el objeto de origen debe estar en un estado válido. ¿Cómo fue el objeto de origen mal formada consiguió construido en el primer lugar ?! constructores no deben hacer el trabajo de validación? no son los que modifican las funciones miembro responsable de la conservación del tipo de invariante al validar la entrada? ¿Por qué te validar un objeto "válida" en un constructor de copia?

  

No estoy preocupado por el rendimiento. no estamos programando en el PDP-11

Esto es sobre el estilo más elegante, aunque en C ++ el código más elegante tiene las mejores características de rendimiento generalmente.


Debe utilizar un initializer list. En su código, m_str1 se construyó por defecto después le asigna un nuevo valor. El código podría ser algo como esto:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak No debe validar la OMI cont.m_str1 en el copy constructor. Lo que hago, es validar las cosas en constructors. Validación en copy constructor significa que va a copiar un objeto mal formada, en primer lugar, por ejemplo:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

Se debe utilizar una lista de inicialización, y luego la pregunta carece de sentido, como en:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Hay una gran sección de Matthew Wilson imperfecta C ++ que explica todo acerca de las listas inicializador de miembros, y acerca de cómo se se puede utilizar en combinación con const y / o referencias para hacer su código más seguro.

Editar : un ejemplo que muestra la validación y const:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

Como está escrito en este momento (sin calificación de la entrada o salida) de su getter y setter (descriptor de acceso y mutador, si lo prefiere) están logrando absolutamente nada, por lo que también podría simplemente hacer que la cadena pública y hacer con a él.

Si el código real realmente calificar la cadena, y es muy posible que lo que usted está tratando con no es propiamente una cadena en absoluto - en su lugar, es sólo algo que se parece mucho a una cadena. Lo que realmente estás haciendo en este caso es abusar del sistema de tipos, una especie de exposición de una cadena, cuando el tipo real es solamente algo un poco como una cadena. a continuación, usted está proporcionando la incubadora para tratar de hacer cumplir las restricciones que el tipo real se ha comparado con una cadena real.

Cuando se mira desde esa dirección, la respuesta se hace bastante evidente: en lugar de una cadena, con un colocador para hacer el acto cadena como algún otro tipo (más restringida), lo que debe hacer en su lugar es la definición de un verdadero clase para el tipo que realmente quiere. Una vez definida la clase correcta, se hace una instancia del mismo público. Si (como parece ser el caso aquí) que es razonable para asignarle un valor que comienza como una cadena, entonces esa clase debe contener un operador de asignación que toma una cadena como argumento. Si (como también parece ser el caso aquí) que es razonable para convertir ese tipo en una cadena en algunas circunstancias, también puede incluir operador de conversión que produce una cadena como el resultado.

Esto proporciona una mejora real sobre el uso de un setter y getter en una clase que lo rodea. En primer lugar, cuando se pone en una clase de los alrededores, que es fácil de código dentro de esa clase para eludir el captador / definidor, perdiendo el cumplimiento de lo que el compositor tenía que cumplir. En segundo lugar, mantiene una notación de aspecto normal. El uso de un getter y setter unas fuerzas que escribir código que es simplemente feo y difícil de leer.

Uno de los principales puntos fuertes de una clase de cadena en C ++ es el uso de la sobrecarga de operadores para que pueda reemplazar algo como:

strcpy(strcat(filename, ".ext"));

por:

filename += ".ext";

para mejorar la legibilidad. Pero mira lo que pasa si esa cadena es parte de una clase que nos obliga a pasar por un getter y setter:

some_object.setfilename(some_object.getfilename()+".ext");

En todo caso, el código C es realmente más fácil de leer que este lío. Por otro lado, considere lo que sucede si hacemos bien el trabajo, con un objeto público de una clase que define una cadena de operador y el operador =:

some_object.filename += ".ext";

Bonita, sencilla y fácil de leer, como debe ser. Mejor aún, si tenemos que hacer cumplir algo acerca de la cadena, podemos inspeccionar sólo eso pequeña clase, en realidad sólo tenemos que buscar uno o dos, conocidos lugares específicos (operador =, posiblemente un ctor o dos para esa clase) a saber que siempre se hace cumplir. - una historia totalmente diferente de cuando estamos usando un regulador para tratar de hacer el trabajo

Pregúntese cuáles son los costos y beneficios.

Costo: mayor tiempo de ejecución por encima. Llamar a las funciones virtuales en ctors es una mala idea, pero setters y getters son poco probable que sea virtual.

Beneficios: si el setter / getter hace algo complicado, no estás repitiendo código; si se hace algo poco intuitivo, no estás olvidando de hacer eso.

La relación costo / beneficio será diferente para diferentes clases. Una vez que estés comprobado que la relación, utilice su juicio. Para las clases inmutables, por supuesto, usted no tiene los emisores, y que no es necesario captadores (como miembros const y referencias pueden ser públicos, ya que nadie puede cambiar / vuelva a colocar ellos).

No hay bala de plata como la forma de escribir el constructor de copia. Si su clase sólo tiene miembros que proporcionan un constructor de copia que crea instancias que no comparten estado (o al menos no parecen hacerlo) utilizando una lista de inicialización es una buena manera.

De lo contrario, tendrá que pensar en realidad.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

Tenga en cuenta que se puede obtener en torno a la violación de segmento potencial mediante el uso de smart_ptr -., Pero puede tener un montón de diversión depurar los errores resultantes

Por supuesto, puede ser aún más divertido.

  • Miembros que se crean en la demanda.
  • new beta(a.beta) es mal en caso de que de alguna manera introducir polimorfismo.

... un tornillo en el lo contrario -. Por favor, piense siempre al escribir un constructor de copia

¿Por qué necesita captadores y definidores en todo?

simple :) - Conservan invariantes - es decir, garantiza su clase hace, como "MiCadena siempre tiene un número par de caracteres"

.

Si se aplica según lo previsto, su objeto es siempre en un estado válido - por lo que una copia miembro por miembro puede muy bien copiar los miembros directamente sin miedo a romper ninguna garantía. No hay ninguna ventaja de pasar estado ya validado a través de otra ronda de validación estado.

Como dijo Arak, el mejor sería utilizar una lista de inicialización.


No es tan simple (1):
Otra razón para usar getters / setters no se basa en los detalles de implementación. Esa es una idea extraña para un ctor copia, al cambiar estos detalles de implementación que casi siempre se necesita ajustar CDA de todos modos.


No es tan simple (2):
Para demostrar que estoy equivocado, puede construir invariantes que son dependientes de la propia instancia, u otro factor externo. Una (muy contrieved) ejemplo: "si el número de casos es par, la longitud de cadena es par, de lo contrario es extraño." En ese caso, la copia ctor tendría que tirar, o ajustar la cadena. En tal caso, podría ayudar el uso de fijadores / getters - pero eso no es el cas general. Usted no debe derivar reglas generales de rarezas.

Yo prefiero usar una interfaz para las clases externas para acceder a los datos, en caso de que desee cambiar la forma en que está recuperada. Sin embargo, cuando estás dentro del ámbito de la clase y desea replicar el estado interno del valor copiado, me gustaría ir con los miembros de datos directamente.

Por no mencionar que es probable que ahorrar un par de llamadas de función si el comprador no están inline.

Si los captadores son (en línea y) no virtual, no hay ventajas ni desventajas en ellos usando WRT acceso miembro directo - que sólo se ve ridículo a mí en términos de estilo, pero, no es gran cosa de cualquier manera

Si los captadores son virtuales, entonces hay es de arriba ... pero sin embargo eso es exactamente cuando lo hace quiere llamar, en caso de que se hayan omitido en una subclase -)

Hay una prueba sencilla que funciona para muchas cuestiones de diseño, éste incluye:. Añadir efectos secundarios y ver lo que rompe

colocador Supongamos que no sólo asigna un valor, pero también escribe registro de auditoría, registra un mensaje o provoca un evento. ¿Quieres que esto suceda para cada propiedad al copiar objeto? Probablemente no -. Por lo que piden setters en constructor es lógicamente incorrecto (incluso si los emisores son, de hecho, sólo las asignaciones)

A pesar de que de acuerdo con otros que hay muchos de nivel de entrada C ++ "no-no" en su muestra, dejando eso a un lado y respondiendo a su pregunta directamente:

En la práctica, tiendo a hacer muchos, pero no todos mis campos miembros * pública, para empezar, y luego moverlos a obtener / establecer cuando sea necesario.

Ahora, será el primero en decir que esto no es necesariamente una práctica recomendada, y muchos médicos aborrecer esto y decir que cada campo debe tener setters / captadores.

Tal vez. Pero me parece que en la práctica esto no siempre es necesario. Por supuesto, causa dolor más tarde cuando cambio un campo de lo público a un captador, ya veces cuando sé qué uso tendrá una clase, que le dan establecido / y crea el campo protegida o privada desde el principio.

YMMV

RF

  • se llama a los campos "variables" - me animo a utilizar ese término sólo para variables locales dentro de una función / método
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top