2009-10-20 81 views
10

Me gustaría saber una buena sintaxis para getters y setters de C++.Getter y setter, punteros o referencias, y buena sintaxis para usar en C++?

private: 
YourClass *pMember; 

el colocador es fácil supongo:

void Member(YourClass *value){ 
    this->pMember = value; // forget about deleting etc 
} 

y el comprador? ¿debo usar referencias o punteros const?

ejemplo:

YourClass &Member(){ 
    return *this->pMember; 
} 

o

YourClass *Member() const{ 
    return this->member; 
} 

cuál es la diferencia entre ellos?

Gracias,

Joe

EDIT:

lo siento, voy a editar mi pregunta ... lo que sé sobre referencias y punteros, que estaba preguntando acerca de las referencias y punteros const, como captadores, ¿cuál sería la diferencia entre ellos en mi código, como en el futuro, qué shoud espero perder si voy de una manera u otra ...

así que supongo que voy a usar punteros const en lugar de referencias

Los punteros const no se pueden eliminar ni configurar, ¿verdad?

+0

es posible que desee editar sus ejemplos para reflejar su edición. En este momento está devolviendo un puntero no const. –

+0

pero entonces yo mismo no podré borrar la memoria? Necesito saber cómo poseer correctamente las variables y dejar que otros llamen a los getters sin modificar la variable subordinada ... – Jonathan

+0

getters y setters son muy estilo Java/C# (no C++). Son el resultado de la dependencia de estos idiomas en la reflexión y los marcos para construir el objeto. Getters y Setter destruyen la naturaleza OO de un idioma y explícitamente te permiten inmiscuirte en la estructura interna de un objeto. Esto es apon fruncido en C++ si es más común tener el objeto completamente formado después de que el constructor esté completo. –

Respuesta

15

Como una ley general:

  • Si NULL es un parámetro válido o valor de retorno, el uso de punteros.
  • Si NULL es NOT un parámetro válido o valor de retorno, use referencias.

Así que si el setter se puede llamar con NULL, utilice un puntero como parámetro. De lo contrario, use una referencia.

Si es válido llamar al captador de un objeto que contiene un puntero NULL, debe devolver un puntero. Si tal caso es un invariante ilegal, el valor de retorno debería ser una referencia. El getter debe lanzar una excepción, si la variable miembro es NULL.

+0

¿qué tal si: null no es un parámetro válido o un valor de retorno, use return by value? (sin punteros, sin referencias) – hasen

+0

Eso es demasiado simplificado. Pero, de nuevo, la pregunta es de forma generalizada para responder correctamente. –

+0

Usaría el retorno por valor solo para los complementos, por lo demás preferiría evitar el costo de la construcción de copias. // para getters, por supuesto, si necesitas calcular algo, entonces es muy diferente –

2

¿Cuál es la diferencia entre ellos?

La referencia es un alias de la cosa (que es la cosa *). Un puntero es la dirección de la cosa. Si existe la posibilidad de que lo que se señala no esté allí, entonces probablemente no desee devolver las referencias. Las referencias le dicen a la persona que llama "Voy a darle un alias que existirá cuando se lo devuelva". De hecho, no hay manera de verificar la referencia para ver si lo que está debajo es válido.

Con el puntero, semánticamente, está dando a entender que la persona que llama puede comprobar si el miembro existe antes de usarlo. Usualmente esto se hace con una verificación NULL.

En última instancia, no hay una respuesta "correcta". Depende del contrato de la clase y si la persona que llama/deberá/quiere verificar si "Miembro" todavía está presente.

La respuesta breve es punteros para las cosas que se pueden señalar en otro lugar y referencias para los alias "sin ubicar".

+0

lo siento, voy a editar mi pregunta ... Sé sobre referencias y punteros, estaba preguntando sobre referencias y punteros, como getters, lo que haría ser la diferencia entre ellos en mi código, como en el futuro, lo que debería esperar perder si voy de una manera u otra ... – Jonathan

7

Lo mejor es proporcionar una interfaz OO real para el cliente que oculte los detalles de implementación. Getters y Setters no son OO.

+0

Lo soy, pero me gustan los getters y setters en lugar de las variables públicas. Apenas uso variables públicas, solo cuando sea necesario, o al menos las variables protegidas. – Jonathan

+6

Creo que lo que DevFred sugiere es que evites tanto getters/setters AND variables públicas. En su lugar, intente definir los métodos activos que hacen el trabajo por usted. – Boojum

+0

¿me puede dar un ejemplo? Estoy creando clases abstractas y derivando de ellas y la mayoría de mis getters, setters y funciones son virtuales ... Supongo que no entendí lo que quería decir. – Jonathan

1

+1 al cuestionar el uso de incubadoras y captadores. Si debe usarlos y tiene la posibilidad de null, considere usar boost :: shared_ptr. De esta manera, la propiedad se maneja por usted.

+0

No me gustaría usar boost todavía, como estoy aprendiendo, me gustaría crear este proyecto con las menos bibliotecas externas posibles ... – Jonathan

+0

'boost :: shared_ptr' ahora es' std :: shared_ptr' en C++ moderno , por lo que la dependencia externa ya no es una excusa para no usar punteros inteligentes. – ulidtko

4

Como han dicho otros, use punteros si es nula una posibilidad.

En la mayoría de los casos, prefiero usar referencias cuando sea posible. Personalmente, en mi código, me gusta usar la distinción entre punteros y referencias a la propiedad de la señal. Pienso en llamadas con referencias como "prestar" un objeto a otra función o clase. La clase original que pasó o devolvió la referencia todavía la posee, y es responsable de su creación, mantenimiento y limpieza. Cuando mi código pasa un puntero no const, por otro lado, generalmente significa que hay algún tipo de transferencia o intercambio de propiedad en curso, con todas las responsabilidades que eso conlleva.

(Y sí, que suelen utilizar punteros inteligentes. Esos son semejantes a las referencias en mi mente. Estoy hablando de código de nivel más bajo que aquí.)

0

Jonathan, lo compilador está usando? Existe una gran posibilidad de que shared_ptr ya venga incluido como parte de la implementación del TR1 del compilador.

6

Tu código se ve muy bien como si estuvieras acostumbrado a un idioma diferente: en C++, usar this->x (por ejemplo) es relativamente inusual. Cuando el código está bien escrito, también lo está utilizando un descriptor de acceso o un mutador.

Aunque soy bastante inusual en este aspecto en particular, voy a dejar constancia (una vez más) diciendo que obligar al código del cliente a usar un acceso directo o un mutador directamente es una mala idea. Si honestamente tiene una situación en la que tiene sentido que el código del cliente manipule un valor en su objeto, entonces el código del cliente debe usar una asignación normal para leer y/o escribir ese valor.

Cuando/si necesita controlar qué valor se le asigna, la sobrecarga del operador le permite tomar ese control sin forzar la sintaxis de obtención/configuración desagradable en el código del cliente. Específicamente, lo que quiere es una clase de proxy (o plantilla de clase). Solo por un ejemplo, una de las situaciones más comunes donde las personas quieren obtener/configurar funciones es algo así como un número que se supone que está restringido a un rango particular. El setXXX comprueba el nuevo valor para estar dentro del rango, y el getXXX devuelve el valor.

Si desea que, una (bastante) plantilla simple puede hacer el trabajo mucho más limpia:

template <class T, class less=std::less<T> > 
class bounded { 
    const T lower_, upper_; 
    T val_; 

    bool check(T const &value) { 
     return less()(value, lower_) || less()(upper_, value); 
    } 

    void assign(T const &value) { 
     if (check(value)) 
      throw std::domain_error("Out of Range"); 
     val_ = value; 
    } 

public: 
    bounded(T const &lower, T const &upper) 
     : lower_(lower), upper_(upper) {} 

    bounded(bounded const &init) 
     : lower_(init.lower), upper_(init.upper) 
    { 
     assign(init); 
    } 

    bounded &operator=(T const &v) { assign(v); return *this; } 

    operator T() const { return val_; } 

    friend std::istream &operator>>(std::istream &is, bounded &b) { 
     T temp; 
     is >> temp; 

     if (b.check(temp)) 
      is.setstate(std::ios::failbit); 
     else 
      b.val_ = temp; 
     return is; 
    } 
}; 

Esto también hace que el código mucho más cerca de autodocumentado - por ejemplo, cuando se declara un objeto como: bounded<int>(1, 1024);, es inmediatamente evidente que la intención es un número entero en el rango de 1 a 1024. La única parte que alguien podría encontrar abierta a la pregunta es si 1 y/o 1024 están incluidos en el rango.Esto es considerablemente diferente de definir un int en la clase, y esperar que todos los que ven la clase se den cuenta de que se supone que usan el setXXX para imponer algunos límites (en ese momento desconocidos) sobre los valores que pueden ser asignado

Cuando incrusta uno de estos en una clase, lo convierte en una variable pública, y el rango se sigue aplicando. En el código del cliente, no existe un argumento real sobre la sintaxis: solo está asignando a una variable pública, como lo haría con cualquier otra, con el pequeño detalle de que al intentar asignar un valor fuera de rango emitirá una excepción. En teoría, la clase probablemente debería tomar un parámetro de plantilla de política para especificar exactamente lo que hace en ese caso, pero nunca tuve un motivo real para molestarme con eso.

+2

lo siento, pero mirar ese código hace que sea mucho más difícil (al menos para mí) entender que un getter o un setter. No es fácil para mí pensar que crear una clase con plantillas o simplemente una clase para una regla simple es mejor que una simple función de conjunto. Pienso en una gran biblioteca que va a ser utilizada por otras bibliotecas o ejecutables, creo que sobrecargar al operador de asignación no es el mejor enfoque. Por supuesto, no soy más que un novato, pero por lo que he estado leyendo, el operador de asignación debería solo se sobrecargue cuando sea estrictamente necesario, como la Rule of Tree que Martin escribió en otro hilo mío – Jonathan

+2

Si todo lo que alguna vez escribirías fuera un getter y un setter, estarías en lo cierto: este código sería más complejo. Sin embargo, cuando puede reemplazar docenas (potencialmente cientos) de getters y setters con una plantilla, simplifica considerablemente el código. Independientemente de eso, centraliza toda la complejidad. Un getter y un setter requieren * all * el código para estar al tanto de la implementación y agregar complejidad para manejarlo. Esto pone toda la complejidad en un solo lugar y simplifica todo el otro código. –

1

Además de las otras respuestas, si elige referencias para el comprador no lo escriba como en su ejemplo:

YourClass &Member(){ 
    return *this->pMember; 
} 

Su captador de hecho permite la configuración, como en instance->Member() = YourClass(); y por lo tanto sin pasar por el colocador. Esto podría no estar permitido si YourClass no se puede copiar, pero es otra cosa que hay que tener en cuenta. Otro inconveniente es que el getter no es const.

lugar, escriba su captador de la siguiente manera:

const YourClass &Member() const { 
    return *this->pMember; 
} 
Cuestiones relacionadas