15-3 嵌套类型(成员类型)
考虑以下短程序:
#include <iostream>
enum class 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; }
bool isCherry() { return m_type == FruitType::cherry; }
};
int main()
{
Fruit apple { FruitType::apple };
if (apple.getType() == FruitType::apple)
std::cout << "I am an apple";
else
std::cout << "I am not an apple";
return 0;
}

这个程序没有任何问题。但由于枚举类 FruitType 旨在与 Fruit 类结合使用,因此让它独立于该类而存在,让我们能够推断它们是如何连接的。
嵌套类型(成员类型)
到目前为止,我们已经看到了具有两种不同类型成员的类类型:数据成员和成员函数。上例中的 Fruit 类同时具备这两个功能。
类类型支持另一种成员:嵌套类型nested types(也称为成员类型member types)。要创建嵌套类型,您只需在类内部的适当访问说明符下定义类型即可。
这是与上面相同的程序,重写为使用 Fruit 类中定义的嵌套类型:
#include <iostream>
class Fruit
{
public:
// FruitType has been moved inside the class, under the public access specifier
// We've also renamed it Type and made it an enum rather than an enum class
enum Type
{
apple,
banana,
cherry
};
private:
Type m_type {};
int m_percentageEaten { 0 };
public:
Fruit(Type type) :
m_type { type }
{
}
Type getType() { return m_type; }
int getPercentageEaten() { return m_percentageEaten; }
bool isCherry() { return m_type == cherry; } // Inside members of Fruit, we no longer need to prefix enumerators with FruitType::
};
int main()
{
// Note: Outside the class, we access the enumerators via the Fruit:: prefix now
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现在定义在类内部,并已重命名为Type——具体原因我们稍后会讨论。
其次,嵌套类型Type被定义在类顶部。嵌套类型名称必须在使用前完整定义,因此通常优先定义。
最佳实践:
将所有嵌套类型定义在类型的顶部。
第三,嵌套类型遵循常规访问规则。Type在public访问限定符下定义,因此类型名及其枚举项可被公共访问。
第四,类类型如同命名空间般构成作用域区域。因此Type的完整限定名是Fruit::Type,而apple枚举项的完整限定名是Fruit::apple。
在类成员内部无需使用全限定名。例如在成员函数 isCherry() 中访问 cherry 枚举时,可省略 Fruit:: 作用域限定符。
类外部必须使用全限定名(如 Fruit::apple)。我们将 FruitType 重命名为 Type,以便通过 Fruit::Type 访问(而非冗余的 Fruit::FruitType)。
最后,我们将枚举类型从带作用域的改为无作用域的。由于类本身现在充当作用域区域,同时使用带作用域的枚举器有些冗余。改为无作用域枚举意味着我们可以使用 Fruit::apple 访问枚举器,而无需像带作用域枚举那样使用更长的 Fruit::Type::apple。
嵌套的typedef和类型别名
类类型也可以包含嵌套的typedef或类型别名:
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
public:
using IDType = int;
private:
std::string m_name{};
IDType m_id{};
double m_wage{};
public:
Employee(std::string_view name, IDType id, double wage)
: m_name { name }
, m_id { id }
, m_wage { wage }
{
}
const std::string& getName() { return m_name; }
IDType getId() { return m_id; } // can use unqualified name within class
};
int main()
{
Employee john { "John", 1, 45000 };
Employee::IDType id { john.getId() }; // must use fully qualified name outside class
std::cout << john.getName() << " has id: " << id << '\n';
return 0;
}
这将输出:

请注意,在类内部我们可以直接使用IDType,但在类外部必须使用全限定名Employee::IDType。
我们在第10.7节——类型别名与别名定义中讨论过类型别名的优势,此处它们同样发挥着相同作用。C++标准库中的类常采用嵌套别名定义,截至本文撰写时,std::string就定义了十个嵌套别名!
嵌套类与外部类成员的访问
类将其他类作为嵌套类型的情况较为罕见,但确实存在。在C++中,嵌套类无法访问外部(包含)类的this指针,因此嵌套类无法直接访问外部类的成员。这是因为嵌套类可独立于外部类进行实例化(此时根本不存在外部类成员可供访问!)
然而,由于嵌套类属于外部类的成员,它们仍可访问外部类作用域内任何私有成员。
以下示例说明此特性:
#include <iostream>
#include <string>
#include <string_view>
class Employee
{
public:
using IDType = int;
class Printer
{
public:
void print(const Employee& e) const
{
// Printer can't access Employee's `this` pointer
// so we can't print m_name and m_id directly
// Instead, we have to pass in an Employee object to use
// Because Printer is a member of Employee,
// we can access private members e.m_name and e.m_id directly
std::cout << e.m_name << " has id: " << e.m_id << '\n';
}
};
private:
std::string m_name{};
IDType m_id{};
double m_wage{};
public:
Employee(std::string_view name, IDType id, double wage)
: m_name{ name }
, m_id{ id }
, m_wage{ wage }
{
}
// removed the access functions in this example (since they aren't used)
};
int main()
{
const Employee john{ "John", 1, 45000 };
const Employee::Printer p{}; // instantiate an object of the inner class
p.print(john);
return 0;
}
这将输出:

嵌套类在一种情况下更为常见。在标准库中,大多数迭代器类都是作为其设计用于遍历的容器的嵌套类实现的。例如,std::string::iterator 就是作为 std::string 的嵌套类实现的。我们将在后续章节中详细探讨迭代器。
嵌套类型与前向声明
嵌套类型可在包含它的类中进行前向声明。随后该嵌套类型可在包含类内部或外部进行定义。例如:
#include <iostream>
class outer
{
public:
class inner1; // okay: forward declaration inside the enclosing class okay
class inner1{}; // okay: definition of forward declared type inside the enclosing class
class inner2; // okay: forward declaration inside the enclosing class okay
};
class inner2 // okay: definition of forward declared type outside the enclosing class
{
};
int main()
{
return 0;
}

然而,嵌套类型不能在其外围类的定义之前进行前向声明。
#include <iostream>
class outer; // okay: can forward declare non-nested type
class outer::inner1; // error: can't forward declare nested type prior to outer class definition
class outer
{
public:
class inner1{}; // note: nested type declared here
};
class outer::inner1; // okay (but redundant) since nested type has already been declared as part of outer class definition
int main()
{
return 0;
}


虽然可以在包含类的定义之后对嵌套类型进行前向声明,但由于包含类本身已包含该嵌套类型的声明,因此这样做是多余的。

浙公网安备 33010602011771号