Вложенные классы похожи на обычные классы, но:
- они имеют дополнительное ограничение доступа (как и все определения внутри определения класса),
- они не загрязняют данное пространство имен , например, глобальное пространство имен. Если вы чувствуете, что класс B так глубоко связан с классом A, но объекты A и B не обязательно связаны, то вы можете захотеть, чтобы класс B был доступен только через область видимости класса A (он будет называться A ::Класс).
Некоторые примеры:
Публично вложенный класс, чтобы поместить его в область видимости соответствующего класса
Предположим, вы хотите иметь класс, SomeSpecificCollectionкоторый будет агрегировать объекты класса Element. Вы можете затем либо:
объявлять два класса: SomeSpecificCollectionи Element- плохо, потому что имя «Элемент» достаточно общее, чтобы вызвать возможное столкновение имен
ввести пространство имен someSpecificCollectionи объявить классы someSpecificCollection::Collectionи someSpecificCollection::Element. Нет риска столкновения имен, но может ли оно стать более подробным?
объявить два глобальных класса SomeSpecificCollectionи SomeSpecificCollectionElement- которые имеют незначительные недостатки, но, вероятно, в порядке.
объявить глобальный класс SomeSpecificCollectionи класс Elementкак его вложенный класс. Затем:
- вы не рискуете столкновением имен, так как Элемент не находится в глобальном пространстве имен,
- в реализации
SomeSpecificCollectionвы ссылаетесь просто Elementи везде SomeSpecificCollection::Element- как - + выглядит так же, как 3., но более понятно
- становится просто, что это «элемент определенной коллекции», а не «конкретный элемент коллекции»
- Видно, что
SomeSpecificCollectionэто тоже класс.
На мой взгляд, последний вариант, безусловно, самый интуитивный и, следовательно, лучший дизайн.
Позвольте мне подчеркнуть - это не большая разница от создания двух глобальных классов с более подробными именами. Это просто крошечная деталь, но imho делает код более понятным.
Введение другой области видимости внутри класса
Это особенно полезно для введения typedefs или перечислений. Я просто выложу пример кода здесь:
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
};
Один тогда позвонит:
Product p(Product::FANCY, Product::BOX);
Но при рассмотрении предложений по дополнению кода Product::часто можно получить список всех возможных значений перечисления (BOX, FANCY, CRATE), и здесь легко ошибиться (строго типизированные перечисления C ++ 0x решают эту проблему, но не берите в голову) ).
Но если вы введете дополнительную область видимости для этих перечислений, используя вложенные классы, вещи могут выглядеть следующим образом:
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
};
Тогда звонок выглядит так:
Product p(Product::ProductType::FANCY, Product::ProductBoxType::BOX);
Затем, введя Product::ProductType::IDE, вы получите только перечисления из предложенной области действия. Это также снижает риск ошибки.
Конечно, это может не понадобиться для небольших классов, но если у вас много перечислений, то это облегчает работу клиентских программистов.
Таким же образом, вы можете «организовать» большую группу typedef в шаблоне, если вам когда-нибудь понадобится. Это полезный шаблон иногда.
Идиома PIMPL
PIMPL (сокращение от Pointer to IMPLementation) - это идиома, полезная для удаления деталей реализации класса из заголовка. Это уменьшает необходимость перекомпиляции классов в зависимости от заголовка класса всякий раз, когда изменяется часть «реализации» заголовка.
Обычно это реализуется с использованием вложенного класса:
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
Это особенно полезно, если полное определение класса требует определения типов из некоторой внешней библиотеки, которая имеет тяжелый или просто уродливый заголовочный файл (например, WinAPI). Если вы используете PIMPL, тогда вы можете заключать любые специфичные для WinAPI функции только в .cppи никогда не включать их в .h.