有人能告诉我一些很好的资源来理解和使用嵌套类吗?我有一些像编程原理之类的材料,还有像IBM知识中心-嵌套类之类的东西
但我还是搞不懂他们的目的。有人能帮帮我吗?
有人能告诉我一些很好的资源来理解和使用嵌套类吗?我有一些像编程原理之类的材料,还有像IBM知识中心-嵌套类之类的东西
但我还是搞不懂他们的目的。有人能帮帮我吗?
当前回答
嵌套类对于隐藏实现细节很酷。
列表:
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的一部分,必须永远维护。通过使类私有,我不仅隐藏了实现,我还说这是我的,我可以随时改变它,所以你不能使用它。
看看std::list或std::map,它们都包含隐藏类(真的吗?)关键是它们可能会也可能不会,但是因为实现是私有的和隐藏的,所以STL的构建者能够在不影响您使用代码的情况下更新代码,或者在STL周围留下许多旧包袱,因为他们需要与一些决定使用隐藏在列表中的Node类的傻瓜保持向后兼容性。
其他回答
我认为使类成为嵌套类而不仅仅是友类的主要目的是能够在派生类中继承嵌套类。在c++中,友谊是无法继承的。
你也可以考虑main函数的first class ass类型,在那里你启动所有需要的类一起工作。例如Game类,初始化所有其他类,如窗口、英雄、敌人、关卡等。这样你就可以从主函数中去掉所有的东西。在这里你可以创建Game对象,也许做一些额外的外部调用与Gemente无关。
嵌套类对于隐藏实现细节很酷。
列表:
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的一部分,必须永远维护。通过使类私有,我不仅隐藏了实现,我还说这是我的,我可以随时改变它,所以你不能使用它。
看看std::list或std::map,它们都包含隐藏类(真的吗?)关键是它们可能会也可能不会,但是因为实现是私有的和隐藏的,所以STL的构建者能够在不影响您使用代码的情况下更新代码,或者在STL周围留下许多旧包袱,因为他们需要与一些决定使用隐藏在列表中的Node类的傻瓜保持向后兼容性。
我不经常使用嵌套类,但我偶尔会使用它们。特别是当我定义某种数据类型,然后我想定义一个为该数据类型设计的STL函子。
例如,考虑一个泛型Field类,它有一个ID号、一个类型代码和一个字段名。如果我想通过ID号或名称搜索这些字段的向量,我可以构造一个函子来这样做:
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_;
};
};
然后需要搜索这些字段的代码可以使用Field类本身的匹配范围:
vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));
嵌套类就像普通类一样,但是:
它们有额外的访问限制(就像类定义中的所有定义一样), 它们不会污染给定的命名空间,例如全局命名空间。如果您觉得类B与类A有很深的联系,但A和B的对象并不一定相关,那么您可能希望类B只能通过确定类A的作用域来访问(它将被称为A:: class)。
一些例子:
公开嵌套类,将其放在相关类的范围中
假设您希望有一个SomeSpecificCollection类,它将聚集类Element的对象。然后你可以:
declare two classes: SomeSpecificCollection and Element - bad, because the name "Element" is general enough in order to cause a possible name clash introduce a namespace someSpecificCollection and declare classes someSpecificCollection::Collection and someSpecificCollection::Element. No risk of name clash, but can it get any more verbose? declare two global classes SomeSpecificCollection and SomeSpecificCollectionElement - which has minor drawbacks, but is probably OK. declare global class SomeSpecificCollection and class Element as its nested class. Then: you don't risk any name clashes as Element is not in the global namespace, in implementation of SomeSpecificCollection you refer to just Element, and everywhere else as SomeSpecificCollection::Element - which looks +- the same as 3., but more clear it gets plain simple that it's "an element of a specific collection", not "a specific element of a collection" it is visible that SomeSpecificCollection is also a class.
在我看来,最后一个变体绝对是最直观的,因此是最好的设计。
让我强调一下——这与创建两个具有更详细名称的全局类没有太大区别。这只是一个很小的细节,但恕我直言,它使代码更清晰。
在类作用域中引入另一个作用域
这在引入类型defs或枚举时特别有用。我将在这里发布一个代码示例:
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);
但是当查看Product::的代码补全建议时,通常会列出所有可能的枚举值(BOX, FANCY, CRATE),在这里很容易犯错误(c++ 0x的强类型枚举可以解决这个问题,但不用介意)。
但是如果你使用嵌套类为这些枚举引入额外的作用域,事情可能会像这样:
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(实现指针的缩写)是一种习语,用于从头文件中删除类的实现细节。这减少了每当头的“实现”部分发生变化时,根据类的头重新编译类的需要。
它通常使用嵌套类实现:
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中。