web-dev-qa-db-ja.com

メンバー関数を使用したC ++構造体とパブリック変数を使用したクラス

これは本当に良い形/ベストプラクティスの問題です。 C++の構造体を使用して、値を取得/設定するだけの処理を行う多数のアクセサメソッドを持つクラスを作成するのではなく、基本的にデータを保持するように設計されたオブジェクトを作成します。例えば:

struct Person {
    std::string name;
    DateObject dob;
    (...)
};

さらに20個の変数を想像すると、プライベートメンバーと40個のアクセサーを持つクラスとしてこれを記述するのは管理が面倒で、私には無駄に思えます。

ただし、データに何らかの最小限の機能を追加する必要がある場合もあります。この例では、dobに基づいて年齢も必要になる場合があるとします。

struct Person {
    std::string name;
    DateObject dob;
    (...)
    int age() {return calculated age from dob;}
}

もちろん、複雑な機能についてはクラスを作成しますが、このような単純な機能については、これは「悪い設計」ですか?クラスを使用する場合、データ変数をパブリッククラスメンバとして保持するのは悪い形式ですか、またはそれを受け入れて一連のアクセサメソッドを持つクラスを作成するだけですか?クラスと構造体の違いを理解しているので、ベストプラクティスについて質問しています。

26
amnesia

ここで考慮すべき2つの重要な設計原則があると思います。

  1. そのクラスに不変条件がある場合、インターフェースを介してクラスの表現を非表示にします。

    クラスに無効な状態などがある場合、クラスには不変式があります。クラスは常に不変式を維持する必要があります。

    2D幾何学的点を表すPointタイプを考えてみましょう。これは、パブリックstructおよびxデータメンバーを持つyである必要があります。無効なポイントなどはありません。 x値とy値のすべての組み合わせは完全に問題ありません。

    Personの場合、不変式があるかどうかは手元の問題に完全に依存します。空の名前などを有効な名前と見なしますか? Personには誕生日がありますか?あなたの場合、答えはイエスだと思うし、あなたのクラスはメンバーを公開しておくべきだ。

    参照: クラスは不変条件を適用する必要があります

  2. 非友達の非メンバー関数はカプセル化を改善します。

    age関数をメンバー関数として実装する必要はありません。 ageの結果は、Personのパブリックインターフェイスを使用して計算できるため、メンバー関数になる理由はありません。 Personと同じネームスペースに配置して、引数依存のルックアップで見つけられるようにします。 ADLで見つかった関数は、そのクラスのインターフェイスの一部です。プライベートデータにはアクセスできません。

    それをメンバー関数にして、ある日、プライベート状態をPersonに導入した場合、不必要な依存関係になります。突然ageは必要以上にデータにアクセスできます。

    参照: 非メンバー関数がカプセル化を改善する方法

だからここに私がそれを実装する方法があります:

struct Person {
  std::string name;
  DateObject dob;
};

int age(const Person& person) {
  return calculated age from person.dob;
}
23

C++では、Structsはクラスであり、onlyの違い(少なくとも私が考えることができる)は、Structsのメンバーはデフォルトでパブリックであるが、クラスではプライベートです。これは、そのまま構造体を使用することは完全に受け入れられることを意味します- この記事 はそれをうまく説明しています。

6
Polar

C++では、構造体とクラスの唯一の違いは、構造体がデフォルトで公開されていることです。優れたガイドラインは、データを保持し、より多くの機能(メンバー関数)が必要な場合にクラスを使用するプレーンオールドデータ(POD)として構造体を使用することです。

クラスにパブリック変数のみを含めるのか、メンバー関数を使用するのか疑問に思うかもしれません。次のシナリオを検討してください。

プライベート変数の単なるゲッターである関数Aを持つクラスGetSomeVariableがあるとしましょう:

class A
{
    double _someVariable;

public:
    double GetSomeVariable() { return _someVariable; }
};

20年後、その変数の意味が変わり、0.5を掛けなければならないとしたらどうでしょうか。ゲッターを使用する場合は簡単です。 0.5を掛けた変数を返すだけです:

    double GetSomeVariable() { return 0.5*_someVariable; }

これにより、メンテナンスが容易になり、変更が簡単になります。

1

データホルダーが必要な場合は、get/setメソッドを持たないstructをお勧めします。

この場合のように、「Person」など、他にもある場合。

  1. 実世界のエンティティをモデル化し、
  2. 明確な状態と動作を持ち、
  3. 外界と相互作用し、
  4. 他のエンティティとの単純/複雑な関係を示し、
  5. 時間の経過とともに進化する可能性があり、

それはクラスの完璧な候補です。

1
Arun

「データを運ぶパッシブオブジェクトにのみ構造体を使用します。他のすべてはクラスです。」

google guidlines と言うと、私はこのようにして、良いルールを見つけます。それに加えて、あなたはあなた自身の語用論を定義するか、それが本当に理にかなっているならこの規則から逸脱することができると思う。

0
citykid

ここで聖戦を輝かせたくありません。私は通常、このように区別します:

  • PODオブジェクト(つまり、データのみ、公開された動作なし)の場合、内部をパブリックに宣言し、直接アクセスします。 structキーワードの使用はここで便利であり、オブジェクトの使用のヒントとしても機能します。
  • 非PODオブジェクトの場合、内部をプライベートに宣言し、パブリックのゲッター/セッターを定義します。これらの場合、classキーワードの使用はより自然です。
0