Вкладені класи - це як звичайні заняття, але:
- у них є додаткове обмеження доступу (як це роблять усі визначення всередині визначення класу),
- вони не забруднюють даний простір імен , наприклад глобальний простір імен. Якщо ви вважаєте, що клас 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
це теж клас.
На мою думку, останній варіант, безумовно, найбільш інтуїтивний і, отже, найкращий дизайн.
Дозвольте наголосити - це не велика різниця від створення двох глобальних класів з більш багатослівними іменами. Це просто крихітна деталь, але їм це робить код більш зрозумілим.
Введення іншої області в межах класу
Це особливо корисно для введення typedefs або enums. Я просто опублікую тут приклад коду:
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, ви отримаєте лише перерахунки з потрібної запропонованої області. Це також зменшує ризик помилки.
Звичайно, це може не знадобитися для невеликих занять, але якщо у вас є багато перераховань, то це полегшує роботу клієнтських програмістів.
Таким же чином, ви могли б "організувати" велику купу типів у шаблоні, якщо у вас коли-небудь виникне потреба. Іноді це корисна модель.
Ідіома PIMPL
PIMPL (скорочення покажчика на 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
.