2010-11-23 9 views
11

Mire a continuación los dos ejemplos simplificados de diseño de una agregación de clase a continuación.agregación de la clase de diseño: asignación de la pila frente a la asignación de la memoria dinámica

Solución 1

Cabecera

// need include, forward declaration is not enough 
#include "door.h" 

class CGarage 
{ 
public: 
    CGarage(const std::string &val); 

private: 
    CDoor m_door; 
}; 

Fuente

#include "garage.h" 
CGarage::CGarage(const std::string &val) 
     :m_door(val) 
{ 
} 

Solución 2

Header

#include "smart_ptr.hpp" 

// forward declaration 
class CDoor; 

class CGarage 
{ 
public: 
    CGarage(const std::string &val); 

private: 
    scoped_ptr<CDoor> m_door; 
}; 

Fuente

#include "garage.h" 
#include "door.h" 

CGarage::CGarage(const std::string &val) 
     :m_door(new CDoor(val)) 
{ 
} 

Las cuestiones relativas a la creación del miembro CDoor

¿Qué ventajas/desventajas que percibe en el diseño de los ejemplos (dinámico asignación de CDoor vs asignación automática)?

Esto es lo que ocurrió:

Solución 1:
+ no hay problemas con el manejo de la memoria o de por vida
+ sin necesidad de asignación de memoria costosa en tiempo de ejecución
- incluyen la necesidad adicional en la cabecera (compilación velocidad más lenta ?, más cerca de acoplamiento a CDoor) -> muchos incluye en los archivos de cabecera se consideran mal ...

Solución 2:
acoplamiento + suelto con CDoor en la cabecera (sólo hacia delante declaración necesario)
- la memoria debe ser manejada por el programador

¿Qué diseño prefiere habitualmente por qué motivo?

+0

La solución 1 no está necesariamente asignada en la pila. Si crea un Garaje a través de nuevo, la puerta también está asignada en el montón. –

+0

@David, tienes razón. Simplemente no pude encontrar palabras mejores para expresar los diferentes tipos para asignar el miembro de CDoor. Abierto para cualquier sugerencia ... – nabulke

+0

@David: El punto es que ambos objetos están asignados en un solo bloque de memoria. Incluso si está en el montón, aún es mejor que 2 asignaciones de montón. – valdo

Respuesta

8

Es raro que obtenemos diseño de las preguntas (quiero decir, los interesantes).

Olvidemos por un momento el ejemplo (obviamente) inventado y concéntrese en la noción.

Tenemos 2 soluciones:

  • duro de contención: tire de la cabecera y construir el objeto directamente
  • suave de contención: declare hacia delante de la cabecera y utilizar un puntero

Descartaré voluntariamente todos los argumentos de "actuaciones" por el momento. El rendimiento no importa el 97% del tiempo (dice Knuth), a menos que mida una diferencia notable, ya que la funcionalidad es idéntica, por lo tanto, no tenemos que preocuparnos por el momento.

lo tanto, tenemos dos conceptos ortogonales tratar de influir en nuestra decisión:

  • Dependencia hacer que nosotros nos inclinamos a suave contención
  • Simplicidad hacer que nosotros nos inclinamos a duro contención

Algunas respuestas aquí tienen rig Hthly habló sobre el polimorfismo, pero la implementación exacta de Door es un detalle que es la preocupación de Door, no Garage. Si Door desea ofrecer varias implementaciones, está bien, siempre y cuando sus clientes no se preocupen por este detalle.

Soy bastante aficionado a los principios KISS y YAGNI.Así que yo discutiría a favor de la contención Duro ... con una advertencia.

Cuando se diseña una interfaz que se expondrá, una interfaz que se encuentra en la frontera de la biblioteca, esta interfaz debe exponer un mínimo de dependencias y elementos internos. Idealmente, esto debería ser un Facade o una Proxy, un objeto cuyo único propósito es ocultar los detalles internos de la biblioteca, y este objeto debe tener dependencias mínimas en su cabecera y tienen compatibilidad máxima de diseño, lo que significa:

  • sin método virtual
  • un puntero simple como un atributo (Pimpl)

Para todas las clases internas, simplicidad gana las manos de encima.

1

No es solo una cuestión de acoplamiento (en realidad, lejos de eso: la asignación dinámica se vuelve realmente interesante si usas polimorfismo). La regla general clásica allí es: si puedes tener el objeto en tu clase, hazlo. Es exactamente lo mismo que en una función: si puede tener una variable local, tómela, no vaya asignando memoria por el bien de la depuración de pesadilla.

Por ejemplo, si va a necesitar una agregación de un número desconocido de componentes, los punteros (compartidos, inteligentes o tontos) son sus amigos. Aquí, por ejemplo, si no sabe cuántas puertas tendrá su garaje, los punteros (en realidad, no compartidos) y la asignación dinámica es una buena idea.

Si tiene un objeto utilizado por otro objeto, que siempre será de la misma clase, y que no es útil después de que su propietario está muerto, ¿por qué tendría que pasar por la asignación dinámica?

En resumen: el contexto lo es todo, pero, por su propio bien, intente tener el menor número posible de objetos dinámicos.

+0

Hay una gran diferencia, y es una cuestión de acoplamiento. En la implementación de una función, no la aumenta de ninguna manera utilizando una variable de ámbito local en lugar de auto_ptr < Foo > foo (nuevo Foo); etc. Habría una diferencia si Door estuviera en cualquier lugar de la interfaz pública de la clase, por lo tanto aquellos que están usando Garajes siempre usarán Puertas de todos modos. – CashCow

1

Para mí estos diseños son equivalentes. En cada caso, CDoor es propiedad de CGarage.

Prefiero 1. dado que el shared_ptr en el segundo no parece agregar nada más que la complejidad - ¿con quién está compartiendo el contenido? CGarage? Tus contras para 1. no son convincentes para mí.

¿Por qué no utilizar scoped_ptr en 2. a menos que esté proporcionando un captador para el objeto CDoor?

+0

sí, tiene razón: shared_ptr solo agrega complejidad innecesaria a mi ejemplo. Lo cambié a un scoped_ptr. – nabulke

+0

porque con scoped_ptr tiene un problema de no poder usar una clase declarada hacia adelante. A veces puede salirse con la suya al mover su implementación de destructor fuera del encabezado. En realidad prefiero usar un puntero sin formato aquí (en particular para pImpls aunque este es un componente no un pImpl). Todavía obedece a RAII cuando los eliminas en tu destructor. Si tiene más de uno de ellos, use temporalmente auto_ptr en su constructor en caso de que se genere una excepción. Al final adjúntelos a sus miembros con la versión() (que nunca tira). – CashCow

0

Solución 1:

usted está exponiendo la cabecera de la puerta, que es sólo un detalle de implementación de la clase. Si Door fuera parte de la interfaz pública de Garage, podría suponer que los usuarios de Garage también usarán Door, pero donde es privado, es mucho mejor no exponerse.

Solución 2:

Usando shared_ptr que significa que si se copia un garaje su copia tiene la misma puerta. No solo como uno similar, el mismo. Si pinta la puerta verde en uno de sus garajes, ambos tendrán puertas verdes. Debes entender ese problema.

La ubicación de su clase en el código juega un papel importante en cuanto a cuál es mejor usar. Si Garage es parte de su interfaz pública y Door no está en ningún lugar en la interfaz pública, entonces es muy beneficioso desacoplarlo, posiblemente utilizando shared_ptr.

Si Garage no es parte de su interfaz pública en cualquier lugar, pero es un detalle de implementación, no me importaría demasiado el tema del acoplamiento.

If Garage and Door están ambos en su interfaz pública. y Door es muy utilizado con Garage, .y Door.h no trae más encabezados, pero es bastante liviano, puede salirse con la agregación como un objeto (incluya el encabezado).

+0

Entiendo los problemas que tengo al copiar una clase con un miembro de puntero inteligente. Omití las funciones/operadores de copia/asignación para mantener el ejemplo simple. – nabulke

2

La solución 1 es superior tanto en tiempo de ejecución como en tiempo de compilación en todos los casos concebibles, a menos que tenga problemas extremos con las dependencias incluidas y debe actuar para reducirlas. La Solución 2 tiene más problemas de los que ha mencionado: para comenzar, necesitará escribir y mantener un operador adicional de copia/constructor de copias.

0

A menos que dos garajes compartan la misma puerta, la solución n. ° 1 como shared_ptr da la impresión de que la puerta está compartida.

+0

Utilizarían la misma puerta si copia el garaje. – CashCow

+0

Ok, quizás este uso de un shared_ptr está dando una impresión equivocada. Scoped_ptr o manejar la memoria yo mismo llamando a new/delete podría ser mejor.Pero, ¿debo llamar nuevo o simplemente ir con la solución 1? – nabulke

0

Algunas cuestiones a tener en cuenta:

Solución 1 (suponiendo que CDoor no es un typedef para un tipo de puntero):

  • no es "polimorfismo amigable" ya que va copiar objetos por valor de inicialización (incluso si pasa por referencia). Por favor, consulte la sección "rebanar clase" cuestión: What is object slicing?
  • No se puede poner en práctica idioma pimpl para hacer frente rápida/inicialización de CGarage

En general, (1) significa que CGarage está estrechamente compled con CDoor.Por supuesto, se puede lograr un poco más FLEXIBILIDAD si CDoor es una especie de adaptador/decorador

Solución 2:

  • clases, junto con menos fuerza
  • asignación del montón Caro
  • costes adicionales para el puntero inteligente

No se puede preferir ninguno de los dos diseños "por lo general", esto depende completamente de lo que modele su clase, de qué es responsable y cómo se usará.

Si le puede recomendar más, investigue los "patrones de diseño C++" sujetos a obtener más información.

Estos deberían ser bueno para empezar:

Cuestiones relacionadas