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

image

这个程序没有任何问题。但由于枚举类 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;
}

image

这里有几点值得指出。

首先,请注意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;
}

这将输出:

image

请注意,在类内部我们可以直接使用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;
}

这将输出:

image

嵌套类在一种情况下更为常见。在标准库中,大多数迭代器类都是作为其设计用于遍历的容器的嵌套类实现的。例如,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;
}

image

然而,嵌套类型不能在其外围类的定义之前进行前向声明。

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

image

image

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

posted @ 2026-01-02 10:01  游翔  阅读(10)  评论(0)    收藏  举报