C++ ゲッター/セッターのコーディング スタイル
-
09-09-2019 - |
質問
私はしばらく C# でプログラミングをしていましたが、今は C++ スキルを磨きたいと思っています。
クラスを持つ:
class Foo
{
const std::string& name_;
...
};
最善のアプローチは何でしょうか (name_ フィールドへの読み取りアクセスのみを許可したいのですが):
- getter メソッドを使用します。
inline const std::string& name() const { return name_; }
- フィールドは定数なので公開します
ありがとう。
解決
これは、非constのフィールドを公開するために悪い考えである傾向がある。
あなたのケースでは、あなたは、constのフィールドを持っているので、上記の問題は問題ではありません。公共の場作りの主な欠点は、あなたが基本となる実装をロックダウンしていることです。将来的にはあなたがC文字列またはUnicode文字列、または何か他のものへの内部表現を変更したい場合たとえば、あなたは、すべてのクライアントコードを破ると思います。新しいゲッターを経由して、新規ユーザーに新しい機能を提供しながらゲッターを使用すると、既存のクライアントのためのレガシー表現に変換することができます。
私はまだあなたが上に置かれているようなgetterメソッドを持つことをお勧めしたいです。これは、あなたの将来の柔軟性を最大化します。
他のヒント
getterメソッドを使用して、それが将来的にはより複雑なものとgetterメソッドを交換することができますよう長命のクラスのためのより良い設計上の選択です。これはconstの値のために必要となる可能性が低いようですが、コストが低く、可能な利益が大きいます。
将来的にはあなたが実際にメソッドのペアを変更することができるのでさておき、C ++で、それは、の同じ名前のメンバーのgetterとsetterの両方を与えるために特に良いアイデアだと:
class Foo {
public:
std::string const& name() const; // Getter
void name(std::string const& newName); // Setter
...
};
毎operator()()
を定義する単一のパブリックメンバ変数に
// This class encapsulates a fancier type of name
class fancy_name {
public:
// Getter
std::string const& operator()() const {
return _compute_fancy_name(); // Does some internal work
}
// Setter
void operator()(std::string const& newName) {
_set_fancy_name(newName); // Does some internal work
}
...
};
class Foo {
public:
fancy_name name;
...
};
クライアントコードはもちろん、再コンパイルする必要がありますが、何の構文の変更は必要ありません!明らかに、この変換は、ゲッターが必要とされているconstの値については、同じようにうまく動作します。
はさておき、C ++のように、const参照部材を有することが幾分奇数です。あなたは、コンストラクタのリストでそれを割り当てる必要があります。誰がそのオブジェクトの実際のメモリを所有し、どのようなことがあるが寿命だ?
スタイルについては、私はあなたの陰部を公開したくない他の人と同意します。 :-)私はセッター/ゲッターのために、このパターンが好きです。
class Foo
{
public:
const string& FirstName() const;
Foo& FirstName(const string& newFirstName);
const string& LastName() const;
Foo& LastName(const string& newLastName);
const string& Title() const;
Foo& Title(const string& newTitle);
};
あなたのような何かを行うことができます。この道ます:
Foo f;
f.FirstName("Jim").LastName("Bob").Title("Programmer");
私はC ++ 11のアプローチは、今よりこのようなことだと思います。
#include <string>
#include <iostream>
#include <functional>
template<typename T>
class LambdaSetter {
public:
LambdaSetter() :
getter([&]() -> T { return m_value; }),
setter([&](T value) { m_value = value; }),
m_value()
{}
T operator()() { return getter(); }
void operator()(T value) { setter(value); }
LambdaSetter operator=(T rhs)
{
setter(rhs);
return *this;
}
T operator=(LambdaSetter rhs)
{
return rhs.getter();
}
operator T()
{
return getter();
}
void SetGetter(std::function<T()> func) { getter = func; }
void SetSetter(std::function<void(T)> func) { setter = func; }
T& GetRawData() { return m_value; }
private:
T m_value;
std::function<const T()> getter;
std::function<void(T)> setter;
template <typename TT>
friend std::ostream & operator<<(std::ostream &os, const LambdaSetter<TT>& p);
template <typename TT>
friend std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p);
};
template <typename T>
std::ostream & operator<<(std::ostream &os, const LambdaSetter<T>& p)
{
os << p.getter();
return os;
}
template <typename TT>
std::istream & operator>>(std::istream &is, const LambdaSetter<TT>& p)
{
TT value;
is >> value;
p.setter(value);
return is;
}
class foo {
public:
foo()
{
myString.SetGetter([&]() -> std::string {
myString.GetRawData() = "Hello";
return myString.GetRawData();
});
myString2.SetSetter([&](std::string value) -> void {
myString2.GetRawData() = (value + "!");
});
}
LambdaSetter<std::string> myString;
LambdaSetter<std::string> myString2;
};
int _tmain(int argc, _TCHAR* argv[])
{
foo f;
std::string hi = f.myString;
f.myString2 = "world";
std::cout << hi << " " << f.myString2 << std::endl;
std::cin >> f.myString2;
std::cout << hi << " " << f.myString2 << std::endl;
return 0;
}
私は残念ながら、私は壊れてカプセル化につながることができ、「GetRawData」パブリックアクセサを提供するために必要なLambdaSetter内の基盤となるストレージを使用するために、Visual Studioの2013年にこれをテストしたが、あなたはどちらかそれを残して、あなた自身を提供することができますちょうどTまたはのための貯蔵容器カスタムゲッター/セッターメソッドを書いているときに、「GetRawData」を使うだけの時間があることを確認します。
名前は不変ですが、あなたはまだそれを計算するのではなく、フィールドでそれを格納するオプションを持っている場合があります。 (私はこれは「名前」のためにそうであると認識、しかしのは、一般的なケースを目指しましょう。)そのため、でも一定のフィールドは最高のゲッターの内側にラップされています:
class Foo {
public:
const std::string& getName() const {return name_;}
private:
const std::string& name_;
};
あなたが計算された値を返すようにgetName()
を変更した場合、それはconstの参照を返すことができなかったことに注意してください。それは発信者を変更する必要はありませんので、それは、(モジュロ再コンパイル。)大丈夫です。
基本的にCスタイルの構造体となるクラスを除き、パブリック変数を避けてください。それはちょうどに入るために良い習慣ではありません。
あなたは、クラスのインターフェイスを定義したら、人々はそれを構築し、それに依存しますので、あなたは、(それに加えること以外)それを変更することはできませんかもしれません。変数国民を作ることは、あなたがその変数を持っている必要があり、そしてあなたはそれが、ユーザが必要とするものがあることを確認する必要があることを意味します。
あなたがゲッターを使用する場合は、さて、あなたは現在、その変数に保存されたいくつかの情報を、提供することを約束しています。状況が変化し、そしてあなたは、むしろその変数のすべての時間を維持したくない場合は、アクセスを変更することができます。要件の変更(と私はいくつかの非常に奇妙な要件の変化を見てきました)、そしてあなたは主にこの変数に名時にはその変数に1が必要な場合は、あなただけのゲッターを変更することができます。あなたは、変数公開されている場合、あなたはそれで立ち往生されると思います。
これは常に発生しませんが、私はちょうど私が変数公衆を作る後悔(以降間違っているリスク)したいかどうかを確認するために状況を分析するよりも迅速なゲッターを書くために、それは非常に簡単に見つけます。
メンバ変数がプライベート作りに入るために良い習慣です。コード規格を持つすべてのお店は、おそらく時折メンバー変数公衆を作っ禁止しようとしている、とコードレビューを持つ任意の店はそれのためにあなたを批判する可能性があります。
それは本当に書き込みを容易にするために重要ではありませんたび、より安全な習慣を身に取得します。
複数のC ++のソースからのアイデアを収集し、C ++でのゲッター/セッターのための素晴らしい、まだ非常に簡単な例に入れます:
class Canvas { public:
void resize() {
cout << "resize to " << width << " " << height << endl;
}
Canvas(int w, int h) : width(*this), height(*this) {
cout << "new canvas " << w << " " << h << endl;
width.value = w;
height.value = h;
}
class Width { public:
Canvas& canvas;
int value;
Width(Canvas& canvas): canvas(canvas) {}
int & operator = (const int &i) {
value = i;
canvas.resize();
return value;
}
operator int () const {
return value;
}
} width;
class Height { public:
Canvas& canvas;
int value;
Height(Canvas& canvas): canvas(canvas) {}
int & operator = (const int &i) {
value = i;
canvas.resize();
return value;
}
operator int () const {
return value;
}
} height;
};
int main() {
Canvas canvas(256, 256);
canvas.width = 128;
canvas.height = 64;
}
出力:
new canvas 256 256
resize to 128 256
resize to 128 64
あなたがここにそれをオンラインでテストすることができます: http://codepad.org/zosxqjTXする
PS:FOイヴェット<3
デザインパターンの理論から、 「異なるものをカプセル化」。 「ゲッター」を定義することにより、上記の原理に良好な密着性があります。だから、将来的にメンバーチェンジの実装表現ならば、メンバーは「ゲッター」から復帰する前に「マッサージ」することができます。 「ゲッター」呼び出しが行われ、クライアント側で何のリファクタリングを示唆していない。
よろしく、