Klasy zagnieżdżone są jak zwykłe klasy, ale:
- mają dodatkowe ograniczenia dostępu (jak wszystkie definicje w definicji klasy),
- oni nie zanieczyszczają daną przestrzeń nazw , np globalnej przestrzeni nazw. Jeśli uważasz, że klasa B jest tak głęboko związana z klasą A, ale obiekty A i B niekoniecznie są ze sobą powiązane, możesz chcieć, aby klasa B była dostępna tylko poprzez określenie zakresu klasy A (byłoby to określane jako A ::Klasa).
Kilka przykładów:
Publicznie zagnieżdżaj klasę, aby umieścić ją w zakresie odpowiedniej klasy
Załóżmy, że chcesz mieć klasę, SomeSpecificCollection
która agregowałaby obiekty klasy Element
. Następnie możesz:
zadeklaruj dwie klasy: SomeSpecificCollection
i Element
- złe, ponieważ nazwa „Element” jest wystarczająco ogólna, aby spowodować ewentualne zderzenie nazwy
wprowadzić przestrzeń nazw someSpecificCollection
i zadeklarować klasy someSpecificCollection::Collection
i someSpecificCollection::Element
. Nie ma ryzyka kolizji nazwisk, ale czy może stać się bardziej gadatliwy?
deklarują dwie globalne klasy SomeSpecificCollection
i SomeSpecificCollectionElement
- co ma drobne wady, ale prawdopodobnie jest OK.
deklaruj klasę globalną SomeSpecificCollection
i klasę Element
jako klasę zagnieżdżoną. Następnie:
- nie ryzykujesz żadnych konfliktów nazw, ponieważ Element nie znajduje się w globalnej przestrzeni nazw,
- w realizacji
SomeSpecificCollection
odwołujesz się do sprawiedliwego Element
i wszędzie innego jako SomeSpecificCollection::Element
- który wygląda + - tak samo jak 3., ale bardziej wyraźnie
- staje się po prostu proste, że jest to „element określonej kolekcji”, a nie „konkretny element kolekcji”
- widać, że
SomeSpecificCollection
to także klasa.
Moim zdaniem ostatni wariant jest zdecydowanie najbardziej intuicyjny, a zatem najlepszy.
Pozwólcie, że podkreślę - nie jest to duża różnica w tworzeniu dwóch globalnych klas o bardziej pełnych nazwach. To tylko drobny szczegół, ale imho sprawia, że kod jest bardziej przejrzysty.
Wprowadzenie innego zakresu w zakresie klasy
Jest to szczególnie przydatne do wprowadzania typedefs lub enum. Po prostu opublikuję tutaj przykład kodu:
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
};
Jeden wtedy zadzwoni:
Product p(Product::FANCY, Product::BOX);
Ale patrząc na propozycje uzupełnienia kodu Product::
, często pojawia się lista wszystkich możliwych wartości wyliczenia (BOX, FANCY, CRATE) i łatwo jest tutaj popełnić błąd (wyliczone w C ++ 0x wyliczenia tego rodzaju rozwiązują to, ale nieważne ).
Ale jeśli wprowadzisz dodatkowy zakres dla tych wyliczeń za pomocą klas zagnieżdżonych, rzeczy mogą wyglądać następująco:
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
};
Następnie połączenie wygląda następująco:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Następnie pisząc Product::ProductType::
IDE, otrzymasz tylko wyliczenia z sugerowanego pożądanego zakresu. Zmniejsza to również ryzyko popełnienia błędu.
Oczywiście może to nie być potrzebne w przypadku małych klas, ale jeśli ktoś ma dużo wyliczeń, ułatwia to programistom klienckim.
W ten sam sposób możesz „zorganizować” dużą grupę pism maszynowych w szablonie, jeśli kiedykolwiek zajdzie taka potrzeba. Czasami jest to użyteczny wzór.
Idiom PIMPL
PIMPL (skrót od Pointer to IMPLementation) to idiom przydatny do usuwania szczegółów implementacji klasy z nagłówka. Zmniejsza to potrzebę ponownej kompilacji klas w zależności od nagłówka klasy, ilekroć zmienia się część „implementacyjna” nagłówka.
Zwykle jest implementowany za pomocą zagnieżdżonej klasy:
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
Jest to szczególnie przydatne, jeśli pełna definicja klasy wymaga definicji typów z biblioteki zewnętrznej, która ma ciężki lub po prostu brzydki plik nagłówka (weź WinAPI). Jeśli używasz PIMPL, możesz zawrzeć dowolną funkcjonalność specyficzną dla WinAPI tylko w .cpp
i nigdy go nie włączać .h
.