提问人:bespectacled 提问时间:1/1/2011 最后编辑:Michael Mabespectacled 更新时间:7/18/2023 访问量:220242
为什么要在 C++ 中使用嵌套类?
Why would one use nested classes in C++?
答:
我不怎么使用嵌套类,但我确实时不时地使用它们。特别是当我定义某种数据类型时,然后我想定义一个为该数据类型设计的 STL 函子。
例如,考虑一个具有 ID 号、类型代码和字段名称的泛型类。如果我想通过 ID 号或名称搜索其中的一个,我可能会构造一个函子来这样做:Field
vector
Field
class Field
{
public:
unsigned id_;
string name_;
unsigned type_;
class match : public std::unary_function<bool, Field>
{
public:
match(const string& name) : name_(name), has_name_(true) {};
match(unsigned id) : id_(id), has_id_(true) {};
bool operator()(const Field& rhs) const
{
bool ret = true;
if( ret && has_id_ ) ret = id_ == rhs.id_;
if( ret && has_name_ ) ret = name_ == rhs.name_;
return ret;
};
private:
unsigned id_;
bool has_id_;
string name_;
bool has_name_;
};
};
然后,需要搜索这些 s 的代码可以使用类本身中的 scoped:Field
match
Field
vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));
评论
嵌套类对于隐藏实现细节来说很酷。
列表:
class List
{
public:
List(): head(nullptr), tail(nullptr) {}
private:
class Node
{
public:
int data;
Node* next;
Node* prev;
};
private:
Node* head;
Node* tail;
};
在这里,我不想公开 Node,因为其他人可能会决定使用该类,这会阻碍我更新我的类,因为任何公开的东西都是公共 API 的一部分,必须永远维护。通过将类设为私有,我不仅隐藏了实现,我还说这是我的,我可以随时更改它,这样你就不能使用它了。
看看或者它们都包含隐藏的类(或者它们?关键是它们可能会也可能不会,但是由于实现是私有的并且是隐藏的,因此 STL 的构建者能够在不影响您使用代码的方式的情况下更新代码,或者在 STL 周围留下许多旧包袱,因为他们需要与一些傻瓜保持向后兼容性,这些傻瓜决定使用隐藏在其中的 Node 类。std::list
std::map
list
评论
Node
detail
嵌套类与常规类一样,但是:
- 它们具有额外的访问限制(就像类定义中的所有定义一样),
- 它们不会污染给定的命名空间,例如全局命名空间。如果您觉得类 B 与类 A 有很深的联系,但 A 和 B 的对象不一定相关,那么您可能希望类 B 只能通过作用域 A 类来访问(它被称为 A::Class)。
一些例子:
公开嵌套类,将其放在相关类的作用域中
假设你想要一个类,它将聚合类的对象。然后,您可以:SomeSpecificCollection
Element
声明两个类:和 - bad,因为名称“Element”足够通用,可能会导致可能的名称冲突
SomeSpecificCollection
Element
引入命名空间并声明类和 .没有名称冲突的风险,但它会变得更冗长吗?
someSpecificCollection
someSpecificCollection::Collection
someSpecificCollection::Element
声明两个全局类和 - 这有小缺点,但可能没问题。
SomeSpecificCollection
SomeSpecificCollectionElement
将全局类和类声明为其嵌套类。然后:
SomeSpecificCollection
Element
- 您不会冒任何名称冲突的风险,因为 Element 不在全局命名空间中,
- 在实现中,你只引用 ,其他任何地方都称为 - 看起来 +- 与 3. 相同,但更清晰
SomeSpecificCollection
Element
SomeSpecificCollection::Element
- 它变得很简单,它是“特定集合的元素”,而不是“集合的特定元素”
- 可见,这也是一个类。
SomeSpecificCollection
在我看来,最后一个变体绝对是最直观的,因此也是最好的设计。
让我强调一下 - 这与使用更冗长的名称创建两个全局类没有太大区别。这只是一个很小的细节,但恕我直言,它使代码更加清晰。
在类作用域中引入另一个作用域
这对于引入 typedef 或枚举特别有用。我在这里发布一个代码示例:
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);
但是,在查看 的代码完成建议时,通常会列出所有可能的枚举值(BOX,FANCY,CRATE),并且很容易在这里犯错误(C++0 的强类型枚举可以解决这个问题,但没关系)。Product::
但是,如果您使用嵌套类为这些枚举引入额外的范围,则情况可能如下所示:
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);
然后,通过键入 IDE,将仅从建议的所需范围中获取枚举。这也降低了犯错的风险。Product::ProductType::
当然,对于小类来说,这可能不需要,但如果一个有很多枚举,那么它使客户端程序员的工作变得更容易。
同样,如果你需要的话,你可以在模板中“组织”一大堆 typedef。有时这是一种有用的模式。
PIMPL成语
PIMPL(指向 IMPLementation 的指针的缩写)是一个惯用语,可用于从标头中删除类的实现细节。这样就减少了每当标头的“实现”部分发生更改时,根据类标头重新编译类的需要。
它通常使用嵌套类实现:
X.h:
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
评论
struct Impl; std::auto_ptr<Impl> impl;
这个错误是由赫伯·萨特(Herb Sutter)推广的。不要对不完整的类型使用auto_ptr,或者至少采取预防措施以避免生成错误的代码。
auto_ptr
unique_ptr
~unique_ptr
)
T
unique_ptr
enum class
可以使用嵌套类实现 Builder 模式。特别是在C++中,我个人发现它在语义上更清晰。例如:
class Product{
public:
class Builder;
}
class Product::Builder {
// Builder Implementation
}
而不是:
class Product {}
class ProductBuilder {}
评论
我认为使类嵌套而不仅仅是友元类的主要目的是能够在派生类中继承嵌套类。友谊在C++中不是继承的。
您还可以考虑 main 函数的 first class ass 类型,您可以在其中启动所有需要的类来工作。例如,类游戏,启动所有其他类,如窗口、英雄、敌人、关卡等。这样,您就可以从主功能中摆脱所有这些东西。您可以在其中创建游戏的 obiect,并可能做一些与 Gemente 无关的额外外部调用。
评论
typedef
class