Classes aninhadas são como classes regulares, mas:
- eles têm restrição de acesso adicional (como todas as definições dentro de uma definição de classe),
- eles não poluem o espaço para nome fornecido , por exemplo, espaço para nome global. Se você acha que a classe B está tão profundamente conectada à classe A, mas os objetos de A e B não estão necessariamente relacionados, convém que a classe B seja acessível apenas através do escopo da classe A (ela seria chamada de A ::Classe).
Alguns exemplos:
Aninhando publicamente classe para colocá-la em um escopo de classe relevante
Suponha que você queira ter uma classe SomeSpecificCollection
que agregue objetos da classe Element
. Você pode:
declare duas classes: SomeSpecificCollection
e Element
- ruim, porque o nome "Elemento" é geral o suficiente para causar um possível conflito de nome
introduza um espaço para nome someSpecificCollection
e declare classes someSpecificCollection::Collection
e someSpecificCollection::Element
. Não há risco de conflito de nomes, mas pode ficar mais detalhado?
declarar duas classes globais SomeSpecificCollection
e SomeSpecificCollectionElement
- que tem desvantagens menores, mas provavelmente está OK.
declarar classe global SomeSpecificCollection
e classe Element
como sua classe aninhada. Então:
- você não corre o risco de conflitos de nome, pois o Element não está no espaço de nomes global,
- na implementação de
SomeSpecificCollection
você se refere apenas Element
, e em todos os outros lugares, como SomeSpecificCollection::Element
- que parece + - o mesmo que 3., mas mais claro
- fica bem simples que seja "um elemento de uma coleção específica", não "um elemento específico de uma coleção"
- é visível que
SomeSpecificCollection
também é uma classe.
Na minha opinião, a última variante é definitivamente o design mais intuitivo e, portanto, o melhor.
Deixe-me enfatizar: não é uma grande diferença criar duas classes globais com nomes mais detalhados. É apenas um pequeno detalhe, mas imho torna o código mais claro.
Introduzindo outro escopo dentro de um escopo de classe
Isso é especialmente útil para a introdução de typedefs ou enumerações. Vou apenas postar um exemplo de código aqui:
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
};
Um então chamará:
Product p(Product::FANCY, Product::BOX);
Mas, ao analisar as propostas de conclusão de código para Product::
, geralmente é possível obter todos os valores possíveis de enumeração (BOX, FANCY, CRATE) e é fácil cometer um erro aqui (as enumerações fortemente tipadas do C ++ 0x resolvem isso, mas não importa) )
Mas se você introduzir um escopo adicional para essas enumerações usando classes aninhadas, as coisas podem se parecer com:
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
};
Em seguida, a chamada se parece com:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Ao digitar Product::ProductType::
um IDE, obteremos apenas as enumerações do escopo desejado sugerido. Isso também reduz o risco de cometer um erro.
É claro que isso pode não ser necessário para turmas pequenas, mas se houver muitas enumerações, isso facilitará as coisas para os programadores clientes.
Da mesma forma, você pode "organizar" um grande número de typedefs em um modelo, se você precisar. Às vezes é um padrão útil.
O idioma do PIMPL
O PIMPL (abreviação de Pointer to IMPLementation) é um idioma útil para remover os detalhes de implementação de uma classe do cabeçalho. Isso reduz a necessidade de recompilar classes, dependendo do cabeçalho da classe sempre que a parte "implementação" do cabeçalho é alterada.
Geralmente é implementado usando uma classe aninhada:
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
Isso é particularmente útil se a definição de classe completa precisar da definição de tipos de alguma biblioteca externa que tenha um arquivo de cabeçalho pesado ou apenas feio (use o WinAPI). Se você usar o PIMPL, poderá incluir qualquer funcionalidade específica do WinAPI apenas .cpp
e nunca incluí-la .h
.