私はしばらくの間C#でプログラミングをしてきましたが、今ではC++スキルを磨き上げたいと思っています。
クラスを持つ:
_class Foo
{
const std::string& name_;
...
};
_
最適なアプローチは何ですか(name_フィールドへの読み取りアクセスのみを許可したい):
inline const std::string& name() const { return name_; }
ありがとう。
エラーチェック制約を強制したり、将来的に値の変更に副作用を追加したりすることが難しくなるため、非constフィールドをパブリックにすることは悪い考えです。
あなたの場合、constフィールドがあるので、上記の問題は問題ではありません。パブリックフィールドにする主な欠点は、基になる実装をロックダウンすることです。たとえば、将来的に内部表現をC文字列やUnicode文字列などに変更したい場合は、すべてのクライアントコードを壊すことになります。ゲッターを使用すると、新しいゲッターを介して新しいユーザーに新しい機能を提供しながら、既存のクライアントのレガシー表現に変換できます。
上記のようなゲッターメソッドを使用することをお勧めします。これにより、将来の柔軟性が最大化されます。
ゲッターメソッドを使用することは、ゲッターメソッドを将来より複雑なものに置き換えることができるため、長寿命クラスのより良い設計選択です。これはconst値には必要ない可能性が高いと思われますが、コストは低く、考えられる利点は大きいです。
余談ですが、C++では、メンバーのゲッターとセッターの両方を指定するのが特に良い考えです同じ名前。
_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;
}
残念ながら、これをVisual Studio 2013でテストしました。残念ながら、LambdaSetter内の基礎となるストレージを使用するために、カプセル化が壊れる可能性のある「GetRawData」パブリックアクセサーを提供する必要がありました。または、「GetRawData」を使用するのは、カスタムgetter/setterメソッドを記述するときだけであることを確認してください。
名前は不変ですが、フィールドに保存するのではなく、計算するオプションが必要な場合があります。 (私はこれが「名前」には起こりそうにないことを知っていますが、一般的なケースを目指しましょう。)そのため、定数フィールドでさえゲッター内でラップするのが最適です:
_class Foo {
public:
const std::string& getName() const {return name_;}
private:
const std::string& name_;
};
_
getName()
を変更して計算値を返す場合、const refを返すことができないことに注意してください。呼び出し元に変更を加える必要がないため(モジュロ再コンパイル)、大丈夫です。
本質的にCスタイルの構造体であるクラスを除き、パブリック変数は避けてください。始めるのは良い習慣ではありません。
クラスインターフェイスを定義した後は、追加する以外は変更できない可能性があります。これは、人々がそれを基にして構築するためです。変数をパブリックにするということは、その変数が必要であり、ユーザーが必要とするものを持っていることを確認する必要があることを意味します。
さて、ゲッターを使用する場合、現在その変数に保持されているいくつかの情報を提供することを約束しています。状況が変化し、その変数を常に維持したくない場合は、アクセスを変更できます。要件が変更され(かなり奇妙な要件の変更を見たことがあります)、主にこの変数にある名前が必要な場合がありますが、その変数にある名前が必要な場合は、ゲッターを変更するだけです。変数をパブリックにした場合、あなたはそれで立ち往生するでしょう。
これは常に起こるとは限りませんが、状況を分析して変数を公開することを後悔するかどうかを確認する(そして後で間違ってしまうリスクがある)かどうかを確認するよりも、簡単なゲッターを作成する方がはるかに簡単です。
メンバー変数をプライベートにすることは、良い習慣です。コード標準のあるショップは、たまにメンバー変数を公開することを禁じるでしょうし、コードレビューのあるショップはあなたを批判するでしょう。
書くのが簡単であることが本当に重要でないときはいつでも、より安全な習慣を身につけてください。
複数の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 Yvette <3
デザインパターン理論から。 「さまざまなものをカプセル化する」。 「ゲッター」を定義することにより、上記の原則を順守します。したがって、メンバーの実装表現が将来変更される場合、「ゲッター」から戻る前にメンバーを「マッサージ」することができます。 'getter'呼び出しが行われるクライアント側でコードのリファクタリングが行われないことを意味します。
よろしく、