2010-11-20 12 views
6

En mi trabajo del día a día, a menudo me encuentro escribiendo clases como en este ejemplo simplificado:Clase - cómo proporcionar acceso de sólo lectura a un contenedor miembro de la clase

class CGarage 
{ 
public: 
    CGarage(); 
    ~CGarage(); 
    typedef std::vector<Car> CarCollection; 

private: 
    CarCollection m_Cars; 
}; 

Quiero que los usuarios de CGarage tiene acceso de solo lectura a CarCollection. Para lograr ese objetivo, esas son algunas de las soluciones comunes que todos no son muy satisfactorios:

Solución 1

class CGarage 
{ 
    Car GetCar(CarCollection::size_type index) const; 
    CarCollection::size_type CarCount() const; 
}; 

desventaja principal:

  • falta de iteradores, no puedo utilizar algoritmos STL en automóviles (p. ej. for_each (...))

Solut ión 2

class CGarage 
{ 
    CarCollection::const_iterator CarBegin() const; 
    CarCollection::const_iterator CarEnd() const; 
    CarCollection::size_type CarCount() const; 
}; 

desventaja principal:

  • Una gran cantidad de código repetitivo, si necesita asistencia para otros tipos de iterador (it, reverse_it).

Solución 3

class CGarage 
{ 
    const CarCollection GetCars() const; 
}; 

desventaja principal:

  • costo de copiar CarCollection al regresar por el valor
  • detalles de implementación conocidas por los usuarios de la clase (por ejemplo, no puede cambiar std :: list sin cambiar el código de usuario)

Solución 4

class CGarage 
{ 
    const CarCollection& GetCars() const; 
}; 

desventaja principal:

  • vida útil de referencia CarCollection con destino a tiempo de vida de CGarage
  • detalles
  • de implementación conocidas por los usuarios de la clase

Preguntas

¿Cómo proporcionaría acceso de solo lectura al CarCollection?

¿Su solución cambia, si CarCollection es un vector con punteros al coche?

Si permite el acceso de lectura y escritura a la colección, ¿es aceptable hacer que la colección sea pública?

Gracias por cualquier consejo

Respuesta

5

¿Cómo proporcionaría acceso de solo lectura al CarCollection?

No veo cuál es el problema con la solución 4. Los usuarios de CGarage deben tener claro que una referencia a su colección de automóviles está vinculada a la vida útil del garaje. Si necesitan la colección de automóviles para sobrevivir el garaje, entonces siempre pueden tomar una copia si así lo desean.

Como alternativa, haga que CGarage mantenga un shared_ptr en la colección de automóviles y devuélvalo, pero yo no lo recomendaría.

¿Su solución cambia, si CarCollection es un vector con punteros al coche?

Para colecciones de objetos propios (es decir, tipos de referencia), es mejor utilizar un contenedor diferente. Los contenedores std:: están diseñados para tipos de valor, y no manejan muy bien los tipos de referencia (especialmente constness). Para estos, use algo como Boost's ptr_vector.

Si permite el acceso de lectura y escritura a la colección, ¿es aceptable hacer que la colección sea pública?

Depende de su situación específica. ¿Es probable que la semántica de la colección cambie? De lo contrario, puede hacerlo público de manera segura (por ejemplo, std::pair). Sin embargo, no recomendaría que hicieras esto por un problema específico del dominio.

+0

Peter, gracias por su respuesta detallada. Una pregunta: usando solución 4 y un contenedor std :: vector . ¿Cómo evitas que el usuario cambie los automóviles (él tiene punteros que no son const para los autos reales, así que puede cambiarlos)? – nabulke

+1

En ese caso, necesitas usar algo como boost 'ptr_vector' (http://www.boost.org/doc/libs/1_45_0/libs/ptr_container/doc/ptr_vector.html), ver también (http: // www .codeproject.com/KB/stl/ptr_vecto.aspx) –

0

puedo responder a uno:

Si usted permite el acceso de lectura a la colección, ¿es aceptable para que el público la colección?

No necesariamente. ¿Qué ocurre si necesita cambiar la implementación o necesita ejecutar un código cada vez que se cambia la variable? E incluso si no hace esto, puede que necesite hacerlo en el futuro, y hacer un cambio de una var pública a un conjunto/obtener puede romper una gran cantidad de código.

+0

Estoy de acuerdo. Pero, ¿cómo implementaría el acceso de lectura/escritura sin mostrar los detalles de implementación del contenedor? – nabulke

0

Opción 4, pero devuelva una referencia a una clase base abstracta que incluye operadores de conversión explícitos a colecciones estándar. Eso al menos reducirá la cantidad de detalles de implementación que se exponen.

Para reducir aún más la dependencia de los detalles de implementación, convierta la clase que contiene la colección a una plantilla, parametrizando el tipo de colección.

1

¿no sería suficiente para declararlo así?

const CarCollection& Cars() { return m_Cars; } 

continuación

CarCollection::const_iterator it = garage.Cars().begin(); 

debería funcionar, pero

CarCollection::iterator it = garage.Cars().begin(); 

daría error.

1

Me gustaría ir con solución 4. En cuanto al problema de la consistencia para contenedores de punteros, puede usar boost::ptr_vector que propaga correctamente la constness (entre otras cosas).

+0

"Propaga correctamente la constness" significa, que si devuelvo un const CarCollection o un const CarCollection y el puntero apuntan a un objeto const car (aunque la colección se define como std :: vector >)? – nabulke

+0

@nabulke sí, pero es boost :: ptr_vector , y tenga cuidado, no es la única diferencia con un std :: vector (ptr_vector tiene la propiedad de los punteros almacenados) – icecrime

1

Depende.

  1. Si los clientes de la clase necesitan un vector de Coches , por ejemplo. ellos suponen que los autos están almacenados contiguamente en la memoria por la razón que sea (aunque no veo otra razón), entonces debes proporcionar una función miembro que devuelva una referencia constante a un vector. No se moleste en pasar el valor y los problemas de seguridad inherentes asociados con la creación de objetos. Si el vector tiene que sobrevivir al objeto Garage, el cliente hará una copia.

  2. Ahora, en cualquier otro caso, el principio de mínimo acoplamiento sugiere que devolvamos un par de iteradores. Después de todo, un par de iteradores es la expresión genérica de una colección . Si el cliente de la clase requiere suposiciones de complejidad, devuelva el tipo correcto de iteradores (y documentéelo).

  3. Para mayor comodidad, y solo cuando tenga sentido, puede sobrecargar operator[]. Un garaje es una colección de autos. Si las ranuras de estacionamiento dentro del garaje están numeradas y desea acceder a los autos con máquinas tragamonedas, puede ser una buena solución.

Sin embargo, tiene razón al decir que C++ iteradores están diseñados de una manera que le obliga a escribir un montón de código repetitivo. Luego, cuando primero escribe la clase, devuelve una referencia constante a un vector, y luego refactoriza.

Cuestiones relacionadas