C++ - 嵌套类有什么作用?

36

我正在学习一些C++,现在正在对抗它与Java的相似之处。我知道Java中内部类的用途,但现在我正在尝试在C++中使用嵌套类,我发现“容器”类的私有属性并不可被嵌套类看到,那么我为什么要使用它们呢?此外,有没有一种方法可以使这些属性可见?


3
一个主要的例子可以是链表。为什么要向外界暴露节点结构呢?它可以成为列表类内部的私有结构。换句话说,这与成员变量、信息隐藏、抽象和封装是相同的。 - Some programmer dude
1
我发现“容器”类的私有属性不会被内部类看到。除非你是从2001年穿越来的时间旅行者,否则你就是错了,或者需要将编译器升级到这个十年的版本。 - Jonathan Wakely
我建议您使用最新的编译器,而不是古老的版本。这样您会有更好的体验。 - Jonathan Wakely
1
可能是 为什么会在 C++ 中使用嵌套类? 的重复问题。 - Hong Ooi
@HongOoi 搜了一下也没看到... :( - Leo91
显示剩余3条评论
3个回答

49
我正在学习一些C++,现在正与Java的相似之处进行对比。首先要注意的是,C++嵌套类与Java中所谓的“静态嵌套类”相似。在C++语法中没有任何东西可以复制Java的“嵌套类”。我发现“容器”类的私有属性不可被内部类看到...
C++ 98:在C++中,内部类与“普通”类没有区别,它们不是类的成员,因此无法访问容器类的私有成员(与其他语言如Java或C#不同)。
C++ 03:
嵌套类是类成员,但仍然受到访问限制(请参见本答案末尾的奇怪事情部分)。它被认为是一个标准缺陷(请参见DR45),因此一些编译器在为C++03编译时先实现了C++0x访问规则(特别是GCC,感谢Jonathan Wakely指出这一点)。

C++11

在C++11中,这个规则已经改变了,现在嵌套类可以访问容器类的私有成员。来自§11.7:

嵌套类是一个成员,因此具有与任何其他成员相同的访问权限。

当然,您仍然需要一个实例才能访问非静态成员。


...那么我为什么应该使用它们?

它们是用来分组相关类的一个实现细节,在使用上可能会有一些与其他语言相同的问题(对于新手来说不够清晰,主要性)。在我看来,它们最大的好处是封装,例如你有这样一个场景:

class stream {
    virtual void write(const std::string text) = 0;
};

class channel {
public:
    virtual stream* get_stream() = 0;

    // Other methods...
};

class tcp_channel : public channel {
public:
    virtual stream* get_stream() {
        return new tcp_stream(this);
    }

private:
    class tcp_stream : public stream { /* implementation */ };
};

在某些情况下,它们也有助于替代嵌套的命名空间:substitute

class protocol {
public:
    virtual void create_connection() = 0;

    class tcp : public protocol { /* implementation */ };
    class shared_memory : public protocol { /* implementation */ };
    class named_pipes: public protocol { /* implementation */ };
};

auto media = protocol::tcp();

或者隐藏实现细节:

class file_system_entry {
public:
    class file : public file_system_entry { };
    class directory : public file_system_entry { };

    std::time_t get_last_modified() { ... }

    void remove() { ... }
    virtual void copy_to(std::string path) = 0;

private:
    class local_handle {
        // Implementation details
    } _handle;
};

有许多其他的使用模式(另请参见为什么要在C ++中使用嵌套类? 进行更好的讨论),只需记住并非每个人都会正确地理解(和使用!)它们。另请参见使用嵌套C ++类和枚举的优缺点?

此外,是否有一种方法可以使这些属性可见?

C++ 11之前你不能(除非你将它们声明为friend,但请参见下一段),如果您需要此功能,请使用支持此功能的C++ 11编译器。GCC已经支持很长时间了,MSVC也是如此,我不知道其他编译器。

嵌套友元

C++ 11访问规则和友元类之间有什么区别?一般来说,它们几乎等价自动访问只是不那么冗长):

class container {
public:
    class nested;
    friend class nested;

    class nested { };
};

相对于:

class container {
public:
    class nested { };
};

然而,使用前向声明会带来一些副作用。同时请记住,从可访问性的角度来看,它们是等效的(就像友元一样,不继承也不可传递)。以下示例无法编译:

class external : public container::nested {
public:
    // No: only class declared inside "container"
    // has access to private members, we do not inherit that 
    void foo(container obj) { /* access a private member of obj*/ }
};

// No, "container" has not access to "nested" private members,
// visibility isn't reciprocal
void container::foo(container::nested obj) {
    // Access some private member of obj
}

// No, we don't have anything to do with container,
// visibility isn't transitive
void friendOfNested(container obj) {
    // Access some private member of obj
}

它们是完全等价的吗?不是,因为在C++ 11中,如果nested是一个嵌套类,则container的友元的私有成员在nested中是可访问的,但如果nestedcontainer的友元,则不可访问。给定以下结构:

class container;

class another {
    friend class container;     
};

class container {
public:
    class nested { };   
};

nested可以访问another的私有成员:

void container::nested::foo(another obj) {
    obj.somePrivateMember = 0;
}

它之所以有效,是因为nestedcontainer的成员,所以友元关系不需要遵循传递性限制。在C++11之前,如果将nested声明为container的友元,则该代码不会编译,因为友元关系不具有传递性。
奇怪的事情
我们可能会认为我们总是可以将嵌套类声明为其容器的友元?实际上标准(SO/IEC 14822:2003(E),11.8)说:
“一个类的友元是不是该类的成员函数或类...”
那么我们不应该能够声明nested作为container的友元:在C++03中,嵌套类是类成员(但标准明确表示它们无法访问容器私有成员,也无法成为容器类的友元)。看来没有希望,幸运的是大多数编译器允许我们这样做(不管标准说了什么)。

1
+1,但你不需要使用头文件技巧。没有什么能阻止你将嵌套类作为包含类的“友元”。 - Angew is no longer proud of SO
2
在C++11之前,你可以使用friend,对吧? - MikeMB
1
这个规则在C++11中发生了改变。不,这个规则是针对C++03而改变的,参见DR 45,该缺陷已于2001年被投票纳入工作文件。 - Jonathan Wakely
1
如果我没记错的话,C++03 TC的最终版本(14882)仍然说“嵌套类的成员没有特殊访问权限...”,而GCC在使用-std=c++03编译时确实遵守了这个规则(我不记得MSVC是怎么做的了,我想它没有)。DR45 _概述了_这个问题(后来在C++11中正式修复)。 - Adriano Repetti
2
@AdrianoRepetti,你是对的,C++03 中实际上措辞并没有改变,但是 GCC 在多年前就在 C++98 模式下实现了这个功能(甚至在它有 -std=c++03 选项之前和 DR45 得到解决之前,参见 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=359,该问题报告于2000年)。我认为2.95是最后一个支持 C++98 规则的版本,那已经是史前时期了。 - Jonathan Wakely
esti qu'cest dla marde - douche_satan

6

它提供了另一种很好的封装技术。将一个类完全放在另一个类的命名空间中,可以降低它在代码库中的可见性。这有助于实现可扩展性并减少维护负担。

函数对象通常以这种方式编码。


那这只是一种封装的好处吗?但为什么他们选择使私有成员对内部类不可见? - Leo91
看起来更好的答案是:C++11似乎已经放宽了这一点。 - Bathsheba

1
Different isn't the same.
Java的内部类创建的对象被认为与外部类的对象相关联,因此在内部类的方法中访问外部类的成员可以在不显式创建指针或引用的情况下完成。C++不会这样做;嵌套类只是另一个类的定义嵌套在另一个类的定义中。这对于封装很方便,但仅限于此:它并不打算通过魔法使该类型的对象了解包含类型的对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接