C++ 嵌套类型(成员类型)详解

请先观察以下简短程序:

#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;
}

该程序完全正确。然而,由于 enum class FruitType 理应与 Fruit 类配套使用,将其独立置于类外,只能依靠命名推断二者关联,显得不够清晰。


嵌套类型(成员类型)

迄今为止,我们见过的类类型仅包含两种成员:数据成员与成员函数。上述 Fruit 类即同时拥有两者。

类类型还支持另一种成员:嵌套类型(亦称 成员类型)。创建嵌套类型时,只需在类内部、于合适的访问说明符之下定义该类型即可。

下面将同一程序改写,使用定义在 Fruit 类内部的嵌套类型:

#include <iostream>

class Fruit
{
public:
    // 将 FruitType 移入类内,置于 public 访问说明符下
    // 并更名为 Type,同时改为非限定枚举(enum)
    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; } // 类内可直接使用 cherry,无需前缀
};

int main()
{
    // 注意:类外访问枚举值需加 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;
}

值得强调的几点:

  1. FruitType 已移入类内并更名为 Type,原因后述。
  2. 嵌套类型 Type 置于类顶部。嵌套类型名必须先完整定义方可使用,故通常放在最前。

最佳实践
请始终在类类型顶部定义任何嵌套类型。

  1. 嵌套类型遵循常规访问控制。Type 位于 public 区域,故外部可直接访问该类型名及其枚举值。
  2. 类类型如同命名空间,为内部声明的名称提供作用域。因此 Type 的完全限定名为 Fruit::Type,枚举值 apple 的完全限定名为 Fruit::apple
    • 类内成员无需使用完全限定名;如 isCherry() 中直接使用 cherry
    • 类外必须使用完全限定名,如 Fruit::apple
  3. 我们改用了非限定枚举(enum),因为类本身已构成作用域;若仍用 enum class,则需书写更冗长的 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; } // 类内可直接使用 IDType
};

int main()
{
    Employee john { "John", 1, 45000 };
    Employee::IDType id { john.getId() }; // 类外需使用完全限定名 Employee::IDType

    std::cout << john.getName() << " has id: " << id << '\n';

    return 0;
}

输出:

John has id: 1

类内可直接使用 IDType;类外则必须用 Employee::IDType
类型别名的优势见第 10.7 课《typedef 与类型别名》。C++ 标准库中的许多类也大量运用嵌套 typedef;截至本文撰写时,std::string 定义了十个嵌套 typedef。


嵌套类与对外部类成员的访问

类包含另一类作为嵌套类型并不常见,但完全可行。
C++ 中,嵌套类不持有外部类的 this 指针,因此无法直接访问外部类成员——嵌套类可独立于外部类实例化(此时不存在外部类成员)。
然而,嵌套类本身是外部类的成员,故可访问外部类的任何 private 成员,只要这些成员在作用域内可见。

示例:

#include <iostream>
#include <string>
#include <string_view>

class Employee
{
public:
    using IDType = int;

    class Printer
    {
    public:
        void print(const Employee& e) const
        {
            // Printer 无法使用 Employee 的 this 指针
            // 因此不能直接访问 m_name、m_id
            // 必须传入 Employee 对象
            // 由于 Printer 是 Employee 的成员,可直接访问 e.m_name、e.m_id
            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 }
    {
    }

    // 本例删去访问函数,因未使用
};

int main()
{
    const Employee john { "John", 1, 45000 };
    const Employee::Printer p {}; // 实例化嵌套类对象
    p.print(john);

    return 0;
}

输出:

John has id: 1

嵌套类更常见的场景在标准库中:大多数迭代器类实现为其所遍历容器的嵌套类。例如 std::string::iteratorstd::string 的嵌套类。后续章节讨论迭代器。


嵌套类型与前向声明

嵌套类型可在其外部类内前向声明,随后再定义,可位于外部类内,也可位于外部类外:

#include <iostream>

class outer
{
public:
    class inner1;   // 允许:在外部类内前向声明
    class inner1 {}; // 允许:在外部类内定义先前前向声明的类型
    class inner2;   // 允许:在外部类内前向声明
};

class inner2 // 允许:在外部类外定义先前前向声明的类型
{
};

int main()
{
    return 0;
}

然而,不可在外部类定义之前前向声明其嵌套类型

#include <iostream>

class outer;         // 允许:可前向声明非嵌套类型
class outer::inner1; // 错误:外部类尚未定义,无法前向声明其嵌套类型

class outer
{
public:
    class inner1 {}; // 嵌套类型在此声明
};

class outer::inner1; // 允许(但冗余),因嵌套类型已随外部类声明

若在外部类定义之后前向声明其嵌套类型,也因外部类已包含嵌套类型声明而变得多余。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"