3

コピー コンストラクターを持つ単純なコンテナー クラスがあります。

ゲッターとセッターを使用すること、またはメンバー変数に直接アクセスすることをお勧めしますか?

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • この例では、すべてのコードがインラインですが、実際のコードにはインライン コードはありません。

更新 (2009 年 9 月 29 日):

これらの回答のいくつかはよく書かれていますが、この質問の要点を見逃しているようです:

  • これは、getter/setter と変数の使用について説明するための単純な不自然な例です

  • イニシャライザ リストまたはプライベート バリデータ関数は、実際にはこの質問の一部ではありません。どちらの設計でもコードの保守と拡張が容易になるかどうか疑問に思っています。

  • この例の文字列に焦点を当てている人もいますが、これは単なる例であり、代わりに別のオブジェクトであると想像してください。

  • 性能は気にしません。私たちはPDP-11でプログラミングしていません

4

11 に答える 11

11

編集:編集された質問に答える:)

これは、getter/setter と 変数の使用について説明するための単純な不自然な例です

変数の単純なコレクションがあり、いかなる種類の検証も追加の処理も必要としない場合は、代わりに POD を使用することを検討してください。Stroustrup の FAQから:

適切に設計されたクラスは、クリーンでシンプルなインターフェイスをユーザーに提示し、 その表現を隠して、ユーザーがその表現について知る必要がないようにします。表現を非表示にすべきでない場合 (たとえば、ユーザーが任意のデータ メンバーを好きなように変更できる必要があるため)、そのクラスを「単純な古いデータ構造」と考えることができます。

つまり、これは Java ではありません。単純なゲッター/セッターは、変数自体を公開するのと同じくらい悪いので、書くべきではありません。

イニシャライザ リストまたはプライベート バリデータ関数は、実際にはこの質問の一部ではありません。どちらの設計でもコードの保守と拡張が容易になるかどうか疑問に思っています。

別のオブジェクトの変数をコピーする場合、ソース オブジェクトは有効な状態である必要があります。そもそも、不正なソース オブジェクトはどのように構築されたのでしょうか?! コンストラクターは検証の仕事をするべきではありませんか? 入力を検証することによってクラスの不変性を維持する責任を負う変更メンバー関数ではありませんか? コピーコンストラクターで「有効な」オブジェクトを検証するのはなぜですか?

性能は気にしません。私たちはPDP-11でプログラミングしていません

これは最もエレガントなスタイルに関するものですが、通常、C++ では最もエレガントなコードが最高のパフォーマンス特性を備えています。


を使用する必要がありますinitializer list。あなたのコードでm_str1は、デフォルトで構築され、新しい値が割り当てられますコードは次のようになります。

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbruleak で IMO 検証cont.m_str1を行うべきではありませんcopy constructor。私がしていることは、 で物事を検証することですconstructors。検証とcopy constructorは、最初に不正な形式のオブジェクトをコピーしていることを意味します。次に例を示します。

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}
于 2009-09-29T02:09:25.563 に答える
8

イニシャライザ リストを使用すると、次のように質問が無意味になります。

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Matthew Wilson のImperfect C++には、メンバー初期化子リストのすべてと、それらを const や参照と組み合わせて使用​​してコードをより安全にする方法について説明している素晴らしいセクションがあります。

編集:検証とconstを示す例:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};
于 2009-09-29T02:12:04.053 に答える
2

現在(入力または出力の修飾なしで)書かれているように、ゲッターとセッター(必要に応じてアクセサーとミューテーター)はまったく何も達成していないので、文字列を公開してそれで済ませたほうがよいでしょう。

実際のコードが実際に文字列を修飾している場合は、処理しているものが適切に文字列ではない可能性が非常に高くなります。代わりに、文字列によく似たものになります。この場合に実際に行っているのは、実際の型が文字列に少し似ている場合に、型システムを悪用して文字列を公開することです。次に、実際の文字列と比較して実際の型が制限を適用しようとするセッターを提供します。

その方向から見ると、答えはかなり明白になります。文字列ではなく、文字列を他の(より制限された)タイプのように動作させるセッターを使用して、代わりに実際のクラスを定義する必要があります。本当に欲しいタイプ。そのクラスを正しく定義したら、そのインスタンスをパブリックにします。(ここでのように)文字列として始まる値を割り当てることが合理的である場合、そのクラスには、引数として文字列を受け取る代入演算子が含まれている必要があります。(ここでもそうであるように)状況によってはその型を文字列に変換することが合理的である場合は、結果として文字列を生成するキャスト演算子を含めることもできます。

これにより、周囲のクラスでセッターとゲッターを使用するよりも大幅に改善されます。何よりもまず、それらを周囲のクラスに配置すると、そのクラス内のコードがゲッター/セッターをバイパスしやすくなり、セッターが強制するはずだったものの強制が失われます。第二に、それは通常の見た目の表記を維持します。ゲッターとセッターを使用すると、単純に醜くて読みにくいコードを書く必要があります。

C ++の文字列クラスの主な長所の1つは、演算子のオーバーロードを使用して、次のようなものを置き換えることができることです。

strcpy(strcat(filename, ".ext"));

と:

filename += ".ext";

読みやすさを向上させるため。しかし、その文字列が、ゲッターとセッターを通過するように強制するクラスの一部である場合はどうなるかを見てください。

some_object.setfilename(some_object.getfilename()+".ext");

どちらかといえば、Cコードは実際にはこの混乱よりも読みやすいです。一方、演算子文字列とoperator =を定義するクラスのパブリックオブジェクトを使用して、正しくジョブを実行するとどうなるかを考えてみましょう。

some_object.filename += ".ext";

あるべき姿と同じように、素晴らしく、シンプルで読みやすい。さらに良いことに、文字列について何かを強制する必要がある場合は、その小さなクラスのみを検査できます。実際には、1つまたは2つの特定のよく知られた場所(operator =、おそらくそのクラスのコンストラクターまたは2つ)を調べるだけで済みます。それは常に強制されていることを知ってください-私たちが仕事をしようとしてセッターを使用しているときとはまったく異なる話です。

于 2009-09-29T02:45:18.033 に答える
1

コピーコンストラクターの書き方に特効薬はありません。クラスに、初期化子リストを使用して状態を共有しない(または少なくとも共有していないように見える)インスタンスを作成するコピーコンストラクターを提供するメンバーしかない場合は、良い方法です。

そうでなければ、実際に考える必要があります。

構造体アルファ{
   ベータ*m_beta;
   alpha():m_beta(new beta()){}
   〜alpha(){m_betaを削除します; }
   alpha(const alpha&a){
     //コピーする必要がありますか?またはあなたは共有状態を持っていますか?コピーオンライト?
     m_beta = new beta(* a.m_beta);
     // 間違い
     m_beta = a.m_beta;
   }

-を使用することで潜在的なセグメンテーション違反を回避できることに注意してください。ただしsmart_ptr、結果として生じるバグのデバッグを楽しむことができます。

もちろん、それはさらに面白くすることができます。

  • オンデマンドで作成されるメンバー。
  • new beta(a.beta)どういうわけかポリモーフィズムを導入する場合は間違っています。

...それ以外の場合はネジ-コピーコンストラクタを作成するときは常に考えてください。

于 2009-09-29T09:37:12.077 に答える
1

コストとメリットが何であるかを自問してください。

コスト: 実行時のオーバーヘッドが高くなります。ctor で仮想関数を呼び出すのは悪い考えですが、setter と getter が仮想である可能性は低いです。

利点: セッター/ゲッターが複雑なことを行う場合、コードを繰り返す必要はありません。それが直感的でないことをするなら、あなたはそれをすることを忘れていません。

費用対効果の比率は、クラスによって異なります。その比率を確認したら、あなたの判断を使用してください。もちろん、不変クラスの場合、セッターはなく、ゲッターも必要ありません (誰も変更/再配置できないため、const メンバーと参照はパブリックになる可能性があるため)。

于 2009-09-29T02:12:13.770 に答える
1

なぜゲッターとセッターが必要なのですか?

シンプル:)-不変条件を保持します-つまり、「MyStringには常に偶数の文字があります」など、クラスが作成することを保証します。

意図したとおりに実装されている場合、オブジェクトは常に有効な状態にあるため、メンバーごとのコピーは、保証を破ることを恐れずにメンバーを直接コピーできます。すでに検証された状態を別の状態検証ラウンドに渡す利点はありません。

AraK が言ったように、最善の方法はイニシャライザ リストを使用することです。


それほど単純ではない (1):
getter/setter を使用するもう 1 つの理由は、実装の詳細に依存しないことです。これは、コピー CTor の奇妙な考えです。このような実装の詳細を変更する場合、ほとんどの場合、とにかく CDA を調整する必要があります。


それほど単純ではありません (2):
私が間違っていることを証明するために、インスタンス自体または別の外部要因に依存する不変条件を構築できます。1 つの (非常に不自然な) 例: 「インスタンスの数が偶数の場合、文字列の長さは偶数であり、そうでない場合は奇数です。」その場合、コピー CTor は文字列をスローまたは調整する必要があります。そのような場合、セッター/ゲッターを使用すると役立つ場合がありますが、それは一般的なケースではありません。奇妙なことから一般的なルールを導き出すべきではありません。

于 2009-09-29T16:21:04.130 に答える
1

文字列がどのように返されるかを予想していますか。空白のトリミング、null のチェックなど? SetMyString() と同じです。答えが「はい」の場合は、コードを無数の場所で変更する必要はなく、getter メソッドと setter メソッドを変更するだけなので、アクセス メソッドを使用したほうがよいでしょう。

于 2009-09-29T02:15:48.483 に答える
0

取得方法を変更したい場合に備えて、外部クラスがデータにアクセスするためのインターフェイスを使用することをお勧めします。ただし、クラスのスコープ内にいて、コピーされた値の内部状態を複製したい場合は、データ メンバーを直接使用します。

ゲッターがインライン化されていない場合、おそらくいくつかの関数呼び出しを節約できることは言うまでもありません。

于 2009-09-29T02:07:32.000 に答える
0

あなたのゲッターが(インラインで)そうでない場合virtual、直接メンバーアクセスに関してそれらを使用することにプラスもマイナスもありません-スタイルの点で私には間抜けに見えますが、どちらにしても大したことではありません.

ゲッターが仮想の場合、オーバーヘッドがあります...しかし、サブクラスでオーバーライドされた場合に備えて、ゲッターを呼び出したいのはまさにそのときです!-)

于 2009-09-29T02:07:39.170 に答える
0

多くの設計上の問題に有効な簡単なテストがあります。これには次のようなものがあります: 副作用を追加し、何が壊れるかを確認します。

セッターが値を割り当てるだけでなく、監査レコードを書き込んだり、メッセージをログに記録したり、イベントを発生させたりするとします。オブジェクトをコピーするときに、すべてのプロパティに対してこれを行いますか? おそらくそうではありません-したがって、コンストラクターでセッターを呼び出すことは論理的に間違っています(セッターが実際には単なる代入であっても)。

于 2009-09-29T15:26:01.870 に答える
-2

私はあなたのサンプルに多くのエントリーレベルの C++ の「いいえ」があるという他のポスターに同意しますが、それを脇に置き、あなたの質問に直接答えます:

実際には、メンバー フィールド* のすべてではありませんが、最初は多くを公開し、必要に応じてそれらを get/set に移動する傾向があります。

ここで、これが必ずしも推奨されるプラクティスではないことを最初に述べます。多くの実践者はこれを嫌い、すべてのフィールドにセッター/ゲッターが必要であると言うでしょう。

多分。しかし、実際には、これは必ずしも必要ではないことがわかりました。確かに、後でフィールドを public から getter に変更すると痛みが生じます。また、クラスの使用方法がわかっている場合は、set/get を指定して、最初からフィールドを保護または非公開にします。

YMMV

RF

  • あなたはフィールドを「変数」と呼んでいます-その用語は関数/メソッド内のローカル変数にのみ使用することをお勧めします
于 2009-09-29T16:01:44.137 に答える