¿Una solución elegante para duplicar, const y non-const, getters?

No lo odias cuando tienes

class Foobar { public: Something& getSomething(int index) { // big, non-trivial chunk of code... return something; } const Something& getSomething(int index) const { // big, non-trivial chunk of code... return something; } } 

No podemos implementar ninguno de estos métodos con el otro, porque no puede llamar a la versión no const desde la versión const (error del comstackdor). Se requerirá un elenco para llamar a la versión de const desde la no- const .

¿Existe una solución realmente elegante para esto? Si no, ¿qué es lo más cercano a uno?

Recuerdo de uno de los libros Effective C ++ que la forma de hacerlo es implementar la versión non-const descartando la const de la otra función.

No es particularmente bonito, pero es seguro. Como la función miembro que lo llama no es const, el objeto en sí no es const, y se descarta la const.

 class Foo { public: const int& get() const { //non-trivial work return foo; } int& get() { return const_cast(static_cast(this)->get()); } }; 

Qué tal si:

 template OUT BigChunk(IN self, int index) { // big, non-trivial chunk of code... return something; } struct FooBar { Something &getSomething(int index) { return BigChunk(this,index); } const Something &getSomething(int index) const { return BigChunk(this,index); } }; 

Obviamente, aún tendrá duplicación de código de objeto, pero no duplicación de código fuente. A diferencia del enfoque const_cast, el comstackdor verificará la corrección de const para ambas versiones del método.

Probablemente necesites declarar las dos instancias interesantes de BigChunk como amigos de la clase. Este es un buen uso del amigo, ya que las funciones de amigo están escondidas cerca de la casa, por lo que no hay riesgo de acoplamiento sin restricciones (¡ooh-er!). Pero no intentaré la syntax para hacerlo ahora. Siéntase libre de agregar.

Lo más probable es que BigChunk necesite deferencia uno mismo, en cuyo caso el orden de definición anterior no va a funcionar muy bien, y se necesitarán algunas declaraciones avanzadas para solucionarlo.

Además, para evitar algunos inconvenientes al encontrar BigChunk en el encabezado y decidir crear una instancia y llamarlo a pesar de que es moralmente privado, puedes mover todo el lote al archivo cpp para FooBar. En un espacio de nombre anónimo. Con enlace interno. Y un cartel que dice “cuidado con el leopardo”.

Yo lanzaría la const a la no-const (segunda opción).

Intenta eliminar los getters refacturando tu código. Use funciones o clases de amigos si solo un número muy pequeño de otras cosas necesita el Algo.

En general, Getters y Setters rompen la encapsulación porque los datos están expuestos al mundo. Usar friend solo expone datos a unos pocos seleccionados, por lo que ofrece una mejor encapsulación.

Por supuesto, esto no siempre es posible, por lo que puede estar atrapado con los getters. Por lo menos, la mayoría o la totalidad del “fragmento no trivial de código” debe estar en una o más funciones privadas, llamadas por ambos getters.

¿Por qué no simplemente sacar el código común en una función privada separada, y luego hacer que los otros dos lo llamen?

La referencia const al objeto tiene sentido (usted está poniendo una restricción en el acceso de solo lectura a ese objeto), pero si necesita permitir una referencia no const , también podría hacer que el miembro sea público.

Creo que esto es a la Scott Meyers (Efficient C ++).

El concepto de ‘const’ está ahí por una razón. Para mí, establece un contrato muy importante en función del cual se escriben más instrucciones de un progtwig. Pero puedes hacer algo en las siguientes líneas:

  1. hacer que tu miembro sea “mutable”
  2. hacer la const ‘getters’
  3. devolver la referencia no const

Con esto, uno puede usar una referencia constante en el LHS si necesita mantener la funcionalidad const donde está usando el captador junto con el uso sin const (peligroso). Pero la responsabilidad ahora está en el progtwigdor para mantener invariantes de clase.

Como se ha dicho antes en SO, eliminar la constness de un objeto const originalmente definido y usarlo es un UB. Por lo tanto, no usaría moldes. Además, hacer una constness de objeto no const y luego volver a eliminar la constness no se vería demasiado bien.

Otra guía de encoding que he visto utilizar en algunos equipos es:

  • Si una variable miembro necesita modificarse fuera de la clase, siempre devuelva un puntero a través de una función miembro no const.
  • Ninguna función miembro puede devolver referencias que no sean const. Solo las referencias de const están permitidas desde las funciones miembro de const.

Esto permite cierta consistencia en la base de código general y la persona que llama puede ver claramente qué llamadas pueden modificar la variable miembro.

Me atrevo a sugerir usar el preprocesador:

 #define ConstFunc(type_and_name, params, body) \ const type_and_name params const body \ type_and_name params body class Something { }; class Foobar { private: Something something; public: #define getSomethingParams \ ( \ int index \ ) #define getSomethingBody \ { \ return something; \ } ConstFunc(Something & getSomething, getSomethingParams, getSomethingBody) };