Вложенные классы похожи на обычные классы, но:
- они имеют дополнительное ограничение доступа (как и все определения внутри определения класса),
- они не загрязняют данное пространство имен , например глобальное пространство имен. Если вы чувствуете, что класс B так глубоко связан с классом A, но объекты A и B не обязательно связаны, то вы можете захотеть, чтобы класс B был доступен только через область видимости класса A (он будет называться A :: Class).
Некоторые примеры:
Публично вложенный класс для помещения его в область видимости соответствующего класса
Предположим, вы хотите иметь класс SomeSpecificCollection
, который бы агрегировал объекты класса Element
. Затем вы можете либо:
объявляют два класса: SomeSpecificCollection
и Element
- плохо, потому что имя «Элемент» достаточно общее, чтобы вызвать возможное столкновение имен
вводит пространство имен someSpecificCollection
и объявляет классы someSpecificCollection::Collection
и someSpecificCollection::Element
. Нет риска столкновения имен, но может ли оно стать более подробным?
объявляют два глобальных класса SomeSpecificCollection
и SomeSpecificCollectionElement
- которые имеют незначительные недостатки, но, вероятно, в порядке.
объявляет глобальный класс SomeSpecificCollection
и класс Element
как его вложенный класс. Тогда:
- вы не рискуете столкновением имен, так как 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) - это идиома, полезная для удаления деталей реализации класса из заголовка. Это уменьшает необходимость перекомпиляции классов в зависимости от заголовка класса, когда изменяется часть «реализации» заголовка.
Обычно это реализуется с использованием вложенного класса:
X.h:
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
.