12.17 – Вложенные типы в классах
Рассмотрим следующую короткую программу:
#include <iostream>
enum FruitType
{
APPLE,
BANANA,
CHERRY
};
class Fruit
{
private:
FruitType m_type;
int m_percentageEaten = 0;
public:
Fruit(FruitType type) :
m_type(type)
{
}
FruitType getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
};
int main()
{
Fruit apple(APPLE);
if (apple.getType() == APPLE)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
В этой программе нет ничего плохого. Но поскольку перечисление FruitType
предназначено для использования вместе с классом Fruit
, немного странно иметь его независимо от самого класса.
Вложенные типы
В отличие от функций, которые не могут быть вложены друг в друга, в C++ типы могут быть определены (вложены) внутри класса. Для этого вы просто определяете тип внутри класса под соответствующим спецификатором доступа.
Вот та же программа, что и выше, с определением FruitType
внутри класса:
#include <iostream>
class Fruit
{
public:
// Обратите внимание: мы переместили FruitType
// внутрь класса под спецификатор открытого доступа
enum FruitType
{
APPLE,
BANANA,
CHERRY
};
private:
FruitType m_type;
int m_percentageEaten = 0;
public:
Fruit(FruitType type) :
m_type(type)
{
}
FruitType getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
};
int main()
{
// Обратите внимание: теперь мы получаем доступ к FruitType через Fruit
Fruit apple(Fruit::APPLE);
if (apple.getType() == Fruit::APPLE)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}
Во-первых, обратите внимание, что FruitType
теперь определен внутри класса. Во-вторых, обратите внимание, что мы определили его под спецификатором открытого доступа, поэтому к определению типа можно получить доступ извне класса.
Классы, по сути, для любых вложенных типов действуют как пространства имен. В предыдущем примере мы могли получить доступ к перечислителю APPLE
напрямую, потому что перечислитель APPLE
был помещен в глобальную область видимости (мы могли бы предотвратить это, используя класс перечисления вместо перечисления, и в этом случае мы получали бы доступ к APPLE
через FruitType::APPLE
). Теперь, поскольку FruitType
считается частью класса, мы получаем доступ к перечислителю APPLE
, добавляя к нему префикс с именем класса: Fruit::APPLE
.
Обратите внимание: поскольку классы перечисления также действуют как пространства имен, если бы мы вложили FruitType
внутрь Fruit
как класс перечисления вместо перечисления, мы получали бы доступ к перечислителю APPLE
через Fruit::FruitType::APPLE
.
Другие типы также могут быть вложенными
Хотя перечисления, вероятно, являются наиболее распространенным типом, вкладываемым внутрь классов, C++ позволяет вам определять внутри класса и другие типы, такие как определения типов typedef
, псевдонимы типов и даже другие классы!
Как и любой обычный член класса, вложенные классы имеют такой же доступ к членам включающего их класса, что и сам включающий их класс. Однако вложенный класс не имеет специального доступа к указателю this
включающего его класса.
Еще одно ограничение вложенных типов – их нельзя объявлять предварительно. Это ограничение может быть снято в будущей версии C++.
Определение вложенных классов не очень распространено, но стандартная библиотека C++ делает это в некоторых случаях, например, с классами итераторов. Мы рассматривали итераторы в уроке «10.24 – Знакомство с итераторами».