利点 1: 名前空間の側面
実際、A
は の名前空間を提供しますB
。これは、コードをより適切に構造化するのに役立ちます。vector
for A
、およびiterator
forを使用した具体的な例を考えてみましょうB
。おそらく、
class vector {
public:
class iterator { /*...*/ };
iterator begin() { /*...*/ }
};
よりも入力しやすく、読みやすく、理解しやすい
class vector_iterator {
/*...*/
};
class vector {
public:
vector_iterator begin() { /*...*/ }
};
特に次の点に注意してください。
2 つのクラス (vector
とiterator
) が互いに依存している場合、つまり互いのメンバーを使用している場合、上記の 2 番目のバージョンでは、2 つのうちの 1 つを前方宣言する必要があり、場合によっては相互の型依存関係によって解決できない状況が発生する可能性があります。(ネストされたクラスを使用すると、ネストされたクラス定義のほとんどの部分で、外側のクラスが完全に定義されていると見なされるため、このような問題を回避するのがはるかに簡単になります。これは §9.2/2 によるものです。)
独自のデータ型を維持する他の多くのデータ型を持っている可能性がありiterator
ますlinked_list
。上記の 2 番目のバージョンを使用するlinked_list_iterator
と、別のクラスとして定義する必要があります。クラス名は、これらの「依存」タイプと代替タイプを追加すると、ますます長く複雑になります。
利点 2: テンプレート
上記の例を続けて、コンテナー (上で定義したvector
およびなど) を引数として取り、それらを反復処理する関数テンプレートを考えてみましょう。linked_list
template <typename Container>
void iterate(const Container &container) {
/*...*/
}
この関数内では、 の反復子型を使用したいと思うことは明らかですContainer
。それがネストされた型である場合、それは簡単です:
typename Container::iterator
しかし、そうでない場合は、反復子の型を別のテンプレート パラメーターとして取得する必要があります。
template <typename Container, typename Iterator>
void iterate(const Container &container) {
/*...*/
Iterator it = container.begin();
/*...*/
}
そして、その反復子の型が関数の引数に現れない場合、コンパイラは型を推測することさえできませんでした。関数を呼び出すたびに、山かっこで明示的に追加する必要がありiterate
ます。
最終的な注意:これは、ネストされたクラスが public または private として宣言されているかどうかとはあまり関係がありません。上記の例は、入れ子になった public 型が明らかに好ましい状況を示唆しています。なぜなら、そのiterator
型はコンテナー クラスの外で使用できるはずだからです。