Las clases anidadas son como las clases regulares, pero:
- tienen restricción de acceso adicional (como todas las definiciones dentro de una definición de clase),
- que no contaminan el espacio de nombres determinado , por ejemplo, espacio de nombres global. Si cree que la clase B está tan profundamente conectada con la clase A, pero los objetos de A y B no están necesariamente relacionados, entonces es posible que desee que la clase B solo sea accesible a través del alcance de la clase A (se denominaría A ::Clase).
Algunos ejemplos:
Clase de anidación pública para ponerla en un ámbito de clase relevante
Suponga que desea tener una clase SomeSpecificCollection
que agregue objetos de clase Element
. Entonces puedes:
declara dos clases: SomeSpecificCollection
y Element
- malo, porque el nombre "Elemento" es lo suficientemente general como para causar un posible choque de nombres
introducir un espacio de nombres someSpecificCollection
y declarar clases someSpecificCollection::Collection
y someSpecificCollection::Element
. No hay riesgo de choque de nombres, pero ¿puede ser más detallado?
declara dos clases globales SomeSpecificCollection
y SomeSpecificCollectionElement
, que tiene inconvenientes menores, pero probablemente esté bien.
declarar clase global SomeSpecificCollection
y clase Element
como su clase anidada. Luego:
- no se arriesga a ningún conflicto de nombres, ya que Element no está en el espacio de nombres global,
- en la implementación de
SomeSpecificCollection
usted se refiere a solo Element
, y en todas partes como SomeSpecificCollection::Element
- que se ve + - igual que 3., pero más claro
- se vuelve simple y simple que es "un elemento de una colección específica", no "un elemento específico de una colección"
- Es visible que
SomeSpecificCollection
también es una clase.
En mi opinión, la última variante es definitivamente el diseño más intuitivo y, por lo tanto, el mejor.
Permítanme enfatizar: no es una gran diferencia hacer dos clases globales con nombres más detallados. Es solo un pequeño detalle, pero en mi opinión, hace que el código sea más claro.
Introducir otro ámbito dentro de un ámbito de clase
Esto es especialmente útil para introducir typedefs o enumeraciones. Voy a publicar un ejemplo de código aquí:
class Product {
public:
enum ProductType {
FANCY, AWESOME, USEFUL
};
enum ProductBoxType {
BOX, BAG, CRATE
};
Product(ProductType t, ProductBoxType b, String name);
// the rest of the class: fields, methods
};
Uno entonces llamará:
Product p(Product::FANCY, Product::BOX);
Pero cuando se miran las propuestas de finalización de código Product::
, a menudo se obtienen todos los valores de enumeración posibles (BOX, FANCY, CRATE) enumerados y es fácil cometer un error aquí (las enumeraciones fuertemente tipadas de C ++ 0x resuelven eso, pero no importa )
Pero si introduce un alcance adicional para esas enumeraciones que usan clases anidadas, las cosas podrían verse así:
class Product {
public:
struct ProductType {
enum Enum { FANCY, AWESOME, USEFUL };
};
struct ProductBoxType {
enum Enum { BOX, BAG, CRATE };
};
Product(ProductType::Enum t, ProductBoxType::Enum b, String name);
// the rest of the class: fields, methods
};
Entonces la llamada se ve así:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Luego, al escribir Product::ProductType::
un IDE, se obtendrán solo las enumeraciones del alcance deseado sugerido. Esto también reduce el riesgo de cometer un error.
Por supuesto, esto puede no ser necesario para clases pequeñas, pero si uno tiene muchas enumeraciones, entonces facilita las cosas para los programadores del cliente.
De la misma manera, podría "organizar" un gran grupo de typedefs en una plantilla, si alguna vez tuvo la necesidad de hacerlo. Es un patrón útil a veces.
El idioma de PIMPL
El PIMPL (abreviatura de puntero a IMPLementation) es una expresión útil para eliminar los detalles de implementación de una clase del encabezado. Esto reduce la necesidad de volver a compilar clases según el encabezado de la clase siempre que cambie la parte de "implementación" del encabezado.
Por lo general, se implementa usando una clase anidada:
Xh:
class X {
public:
X();
virtual ~X();
void publicInterface();
void publicInterface2();
private:
struct Impl;
std::unique_ptr<Impl> impl;
}
X.cpp:
#include "X.h"
#include <windows.h>
struct X::Impl {
HWND hWnd; // this field is a part of the class, but no need to include windows.h in header
// all private fields, methods go here
void privateMethod(HWND wnd);
void privateMethod();
};
X::X() : impl(new Impl()) {
// ...
}
// and the rest of definitions go here
Esto es particularmente útil si la definición de clase completa necesita la definición de tipos de alguna biblioteca externa que tiene un archivo de encabezado pesado o simplemente feo (tome WinAPI). Si usa PIMPL, puede incluir cualquier funcionalidad específica de WinAPI solo en .cpp
y nunca incluirla .h
.