ネストされたクラスを理解して使用するための素敵なリソースを誰かに教えてもらえますか?プログラミング原則などの資料があります IBM Knowledge Center-ネストされたクラス
しかし、私は彼らの目的を理解するのにまだ苦労しています。誰か助けてくれませんか?
ネストされたクラスは、実装の詳細を隠すのに便利です。
リスト:
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の一部であり、維持する必要があるため、クラスの更新が妨げられます( forever。クラスをプライベートにすることで、実装を隠すだけでなく、これが私の実装であると言っているだけでなく、いつでも変更できるので、使用できません。
std::list
またはstd::map
を見てください。これらはすべて非表示のクラスを含んでいます(またはそれらを実行していますか?)。要点はそうであるかもしれないし、そうでないかもしれないが、実装は非公開であり、隠されているため、STLのビルダーはコードの使用方法に影響を与えずにコードを更新でき、必要なために多くの古い荷物をSTLの周りに残しておくことができましたlist
内に隠されていたNodeクラスを使用したいと決めたバカとの後方互換性を維持するため。
ネストされたクラスは通常のクラスに似ていますが、次のとおりです。
いくつかの例:
クラスSomeSpecificCollection
のオブジェクトを集約するクラスElement
が必要だとします。その後、次のいずれかを実行できます。
SomeSpecificCollection
とElement
の2つのクラスを宣言します-名前の衝突を引き起こすには名前「Element」が一般的であるため、不良です
名前空間someSpecificCollection
を導入し、クラスsomeSpecificCollection::Collection
およびsomeSpecificCollection::Element
を宣言します。名前の衝突のリスクはありませんが、これ以上冗長になることはありますか?
2つのグローバルクラスSomeSpecificCollection
およびSomeSpecificCollectionElement
を宣言します。これには小さな欠点がありますが、おそらく大丈夫です。
グローバルクラスSomeSpecificCollection
とクラスElement
をネストされたクラスとして宣言します。次に:
SomeSpecificCollection
の実装では、単にElement
を参照し、他のすべてではSomeSpecificCollection::Element
を参照します。これは、3と同じように見えますが、より明確です。SomeSpecificCollection
もクラスであることがわかります。私の意見では、最後のバリアントは間違いなく最も直感的であり、したがって最高のデザインです。
強調させてください-より冗長な名前を持つ2つのグローバルクラスを作成することと大きな違いはありません。ほんの少しの詳細ですが、コードをより明確にします。
これは、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);
しかし、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(Pointer to 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
これは、完全なクラス定義が、重いまたは単なるjustいヘッダーファイル(WinAPIを使用)を持つ外部ライブラリの型の定義を必要とする場合に特に役立ちます。 PIMPLを使用する場合、WinAPI固有の機能を.cpp
のみで囲み、.h
に含めることはできません。
ネストされたクラスはあまり使用しませんが、時々使用します。特に、ある種のデータ型を定義し、そのデータ型用に設計されたSTLファンクタを定義したい場合は特にそうです。
たとえば、ID番号、型コード、およびフィールド名を持つ一般的なField
クラスを考えます。これらのvector
sのField
を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
sを検索する必要があるコードは、match
クラス自体内でスコープされたField
を使用できます。
vector<Field>::const_iterator it = find_if(fields.begin(), fields.end(), Field::match("FieldName"));
ネストされたクラスでBuilderパターンを実装できます 。特にC++では、個人的には意味的にきれいだと思います。例えば:
class Product{
public:
class Builder;
}
class Product::Builder {
// Builder Implementation
}
のではなく:
class Product {}
class ProductBuilder {}