web-dev-qa-db-ja.com

ネストされたC ++クラスと列挙を使用することの長所と短所?

ネストされたパブリックC++クラスと列挙を使用することの長所と短所は何ですか?たとえば、printerというクラスがあり、このクラスが出力トレイにも情報を格納しているとすると、次のようになります。

class printer
{
public:
    std::string name_;

    enum TYPE
    {
        TYPE_LOCAL,
        TYPE_NETWORK,
    };

    class output_tray
    {
        ...
    };
    ...
};

printer prn;
printer::TYPE type;
printer::output_tray tray;

または:

class printer
{
public:
    std::string name_;
    ...
};

enum PRINTER_TYPE
{
    PRINTER_TYPE_LOCAL,
    PRINTER_TYPE_NETWORK,
};

class output_tray
{
    ...
};

printer prn;
PRINTER_TYPE type;
output_tray tray;

プライベート列挙型/クラスをネストすることの利点はわかりますが、パブリック列挙型に関しては、オフィスが分割されています。スタイルの選択の方が多いようです。

それで、あなたはどちらが好きですか、そしてなぜですか?

44
Rob

ネストされたクラス

クラス内にネストされたクラスには、通常は欠陥と見なされるいくつかの副作用があります(純粋なアンチパターンではない場合)。

次のコードを想像してみましょう:

class A
{
   public :
      class B { /* etc. */ } ;

   // etc.
} ;

あるいは:

class A
{
   public :
      class B ;

   // etc.
} ;

class A::B
{
   public :

   // etc.
} ;

そう:

  • 特権アクセス: A :: Bは、Aのすべてのメンバー(メソッド、変数、シンボルなど)に特権的にアクセスできるため、カプセル化が弱まります。
  • Aのスコープは、シンボルルックアップの候補です。 Bの内側からのコードが表示されます すべて コードを混乱させる可能性のあるシンボルルックアップの候補としてのAからのシンボル
  • 前方宣言: Aの完全な宣言を行わずにA :: Bを前方宣言する方法はありません。
  • 拡張性: Aの所有者でない限り、別のクラスA :: Cを追加することはできません。
  • コードの冗長性: クラスをクラスに入れると、ヘッダーが大きくなるだけです。これを複数の宣言に分割することはできますが、名前空間のようなエイリアス、インポート、または使用法を使用する方法はありません。

結論として、例外がない限り(たとえば、ネストされたクラスはネストされたクラスの親密な部分です...そしてそれでも...)、通常のコードではネストされたクラスに意味がありません。 。

さらに、C++名前空間を使用せずに名前空間をシミュレートする不器用な試みのようなにおいがします。

プロ側では、このコードを分離し、プライベートの場合は使用できないようにしますが、「外部」クラスからは...

ネストされた列挙型

長所:すべて。

短所:何もありません。

事実は、列挙型アイテムがグローバルスコープを汚染することです。

// collision
enum Value { empty = 7, undefined, defined } ;
enum Glass { empty = 42, half, full } ;

// empty is from Value or Glass?

各列挙型を異なる名前空間/クラスに配置することで、この衝突を回避できます。

namespace Value { enum type { empty = 7, undefined, defined } ; }
namespace Glass { enum type { empty = 42, half, full } ; }

// Value::type e = Value::empty ;
// Glass::type f = Glass::empty ;

C++ 0xがクラス列挙型を定義していることに注意してください。

enum class Value { empty, undefined, defined } ;
enum class Glass { empty, half, full } ;

// Value e = Value::empty ;
// Glass f = Glass::empty ;

まさにこの種の問題のために。

44
paercebal

大規模なプロジェクトで大きな問題になる可能性がある1つの欠点は、ネストされたクラスまたは列挙型に対して前方宣言を行うことができないことです。

6
Adam Rosenfield

ネストされたパブリックC++クラスを使用すること自体に長所と短所はありません。事実だけがあります。これらの事実は、C++標準によって義務付けられています。ネストされたパブリックC++クラスに関する事実が賛否両論であるかどうかは、解決しようとしている特定の問題によって異なります。あなたが与えた例では、ネストされたクラスが適切かどうかを判断することはできません。

ネストされたクラスに関する1つの事実は、それらが属するクラスのすべてのメンバーへの特権アクセスを持っているということです。ネストされたクラスがそのようなアクセスを必要としない場合、これは短所です。ただし、ネストされたクラスがそのようなアクセスを必要としない場合は、ネストされたクラスとして宣言されるべきではありません。クラス[〜#〜] a [〜#〜]が特定の他のクラスへの特権アクセスを許可したい場合があります[〜#〜] b [〜#〜]。この問題には3つの解決策があります

  1. [〜#〜] b [〜#〜]の友達にする[〜#〜] a [〜#〜]
  2. [〜#〜] b [〜#〜]ネストされたクラス[〜#〜] a [〜#〜]を作成します
  3. [〜#〜] b [〜#〜]が必要とするメソッドと属性を作成し、[〜#〜] a [〜#〜]のパブリックメンバーにします。

この状況では、カプセル化に違反するのは#3です。これは、[〜#〜] a [〜#〜]が友人とネストされたクラスを制御できるが、パブリックメソッドを呼び出すクラスは制御できないためです。または彼の公開属性にアクセスします。

ネストされたクラスに関するもう1つの事実は、別のクラスを追加することは不可能であるということですA :: C[〜#〜] a [〜#〜]のネストされたクラスとしてあなたは[〜#〜] a [〜#〜]の所有者です。ただし、ネストされたクラスには特権アクセスがあるため、これは完全に合理的です。 A :: C[〜#〜] a [〜#〜]のネストされたクラスとして追加できる場合は、A :: Cだまして[〜#〜] a [〜#〜]特権情報へのアクセスを許可する可能性があります。そして、そのあなたはカプセル化に違反します。これは基本的にfriend宣言と同じです。friend宣言は、友達が他の人から隠している特別な特権を付与しません。それはあなたの友人があなたがあなたの非友人から隠している情報にアクセスすることを可能にします。 C++では、誰かを友達と呼ぶことは利他的な行為であり、利己的な行為ではありません。クラスをネストされたクラスにする場合も同様です。

ネストされたパブリッククラスに関するその他の事実:

  • AのスコープはBのシンボルルックアップの候補です:これが必要ない場合は、[〜#〜] b [〜#〜]の友達にします[〜#〜] a [〜#〜]ネストされたクラスの代わりに。ただし、まさにこの種のシンボルルックアップが必要な場合があります。
  • A :: B前方宣言できません[〜#〜] a [〜#〜]およびA :: Bは密結合です。 A :: Bを知らなくても使用できる[〜#〜] a [〜#〜]は、この事実を隠すだけです。

これを要約すると、ツールがニーズに合わない場合でも、ツールのせいにしないでください。ツールの使用について自分を責めます。他の人は別の問題を抱えているかもしれません、それのためにツールは完璧です。

2
Oswald

私の意見では、独立クラスの実装で作業する以外に依存クラスを使用する予定がない場合は、ネストされたクラスで問題ありません。

「内部」クラスをそれ自体でオブジェクトとして使用したい場合は、物事が少しマンキーになり始め、エクストラクター/インサータールーチンの作成を開始する必要があります。きれいな状況ではありません。

2
Paul Nathan

このように相互に関連するものをグループ化するには、クラスではなく名前空間を使用する必要があるようです。ネストされたクラスを実行する際に私が見た1つの欠点は、セクションを検索しているときに取得するのが難しい非常に大きなソースファイルになってしまうことです。

2
carson

ネストされたクラスは後でいつでもトップレベルに昇格できますが、既存のコードを壊さずに逆のことを実行できない場合があることに注意してください。したがって、最初にネストされたクラスにし、問題が発生し始めた場合は、次のバージョンのトップレベルクラスにすることをお勧めします。

1
Eric G.

paercebalは、ネストされた列挙型について私が言うことすべてを言いました。

WRTネストされたクラス、それらの一般的でほぼ唯一のユースケースは、特定のタイプのリソースを操作するクラスがあり、そのリソースに固有の何かを表すデータクラスが必要な場合です。あなたの場合、output_trayは良い例かもしれませんが、クラスに含まれるクラスの外部から呼び出されるメソッドがある場合、または主にデータクラス以上のものがある場合は、通常、ネストされたクラスを使用しません。また、含まれているクラスが含まれているクラスの外部で直接参照されていない限り、通常、データクラスをネストしません。

したがって、たとえば、printer_manipulatorクラスがある場合、プリンター操作エラーのクラスが含まれている可能性がありますが、プリンター自体は含まれていないクラスになります。

お役に立てれば。 :)

1
Nick

私はあなたの列挙型をクラスに埋め込むことを提唱する投稿に同意しますが、それをしない方が理にかなっている場合があります(ただし、少なくとも名前空間に入れてください)。複数のクラスが異なるクラス内で定義された列挙型を利用している場合、それらのクラスは他の具象クラス(列挙型を所有する)に直接依存しています。そのクラスがその列挙型と他の責任に責任があるので、それは確かに設計上の欠陥を表しています。

したがって、他のコードがその列挙型のみを使用してその具象クラスと直接インターフェイスする場合は、その列挙型をクラスに埋め込みます。それ以外の場合は、名前空間など、列挙型を保持するためのより適切な場所を見つけます。

0
Pat Notz

ネストされたクラスの短所がわかります。ジェネリックプログラミングを使用したほうがよいかもしれません。

小さなクラスが大きなクラスの外部で定義されている場合は、大きなクラスをクラステンプレートにして、将来必要になる可能性のある「小さな」クラスを大きなクラスで使用できます。

ジェネリックプログラミングは強力なツールであり、私見では、拡張可能なプログラムを開発する際にはそれを念頭に置く必要があります。奇妙なことに、誰もこの点について言及していません。

0

列挙型をクラスまたは名前空間に配置すると、列挙型の名前を覚えようとしているときに、インテリセンスがガイダンスを提供できるようになります。確かに小さなことですが、時には小さなことが重要です。

0
Mark Ransom

Visual Studio 2008は、ネストされたクラスにインテリセンスを提供できないようです。そのため、ネストされたクラスを使用していたほとんどの場合、PIMPLイディオムに切り替えました。そのクラスでのみ使用される場合は常に列挙型をクラスに配置し、複数のクラスが列挙型を使用する場合はクラスと同じ名前空間のクラス外に列挙型を配置します。

0
Brian Stewart

私にとって、それを外部に置くことの大きな欠点は、それがグローバル名前空間の一部になることです。列挙型または関連するクラスが実際にそのクラスにのみ適用される場合、それは理にかなっています。したがって、プリンタの場合、プリンタを含むすべてのものが、列挙型PRINTER_TYPEへのフルアクセス権を持っていることを認識しますが、実際にはそれについて知る必要はありません。内部クラスを使用したことがあるとは言えませんが、列挙型の場合、これを内部に保持する方が論理的です。別の投稿者が指摘しているように、グローバル名前空間を詰まらせることは本当に悪いことになる可能性があるため、名前空間を使用して同様のアイテムをグループ化することもお勧めします。私は以前、大規模なプロジェクトに取り組んだことがあり、グローバル名前空間にオートコンプリートリストを表示するのに20分かかります。私の意見では、ネストされた列挙型と名前空間化されたクラス/構造体がおそらく最もクリーンなアプローチです。

0
DavidG