私はStroustrupの(Programming Principles&Practice Using C++)の本でC++の学習に取り組んでいます。演習では、単純な構造体を定義します。
template<typename T>
struct S {
explicit S(T v):val{v} { };
T& get();
const T& get() const;
void set(T v);
void read_val(T& v);
T& operator=(const T& t); // deep copy assignment
private:
T val;
};
次に、val
を取得するためにconstおよび非constメンバー関数を定義するよう求められます。
私は疑問に思っていました:get
を返す非const val
関数を使用することが理にかなっているケースはありますか?
このような状況で間接的に値を変更できないことは、私にははるかに明確に思えます。メンバー変数を返すためにconst関数と非const get
関数が必要なユースケースは何ですか?
非constゲッター?
ゲッターとセッターは慣例にすぎません。ゲッターとセッターを提供する代わりに、時々使用されるイディオムは、
struct foo {
int val() const { return val_; }
int& val() { return val_; }
private:
int val_;
};
そのため、インスタンスのconstnessに応じて、参照またはコピーを取得します。
void bar(const foo& a, foo& b) {
auto x = a.val(); // calls the const method returning an int
b.val() = x; // calls the non-const method returning an int&
};
これが一般的に良いスタイルであるかどうかは意見の問題です。混乱を招く場合と、この動作が予想通りの場合があります(以下を参照)。
いずれにせよ、クラスのインターフェースを設計することは、クラスが何をすることになっているのか、どのように使用したいかに応じて、セッターとゲッターに関する規則に従うことよりも重要です(たとえば、メソッドに意味のある名前を付ける必要があります)これは、「カプセル化されているふりをして、ゲッターを介してすべての内部にアクセスできるようにする」という意味ではなく、それが何をするかを表現しています。
具体例
コンテナ内の要素へのアクセスは通常、このように実装されていると考えてください。おもちゃの例として:
struct my_array {
int operator[](unsigned i) const { return data[i]; }
int& operator[](unsigned i) { return data[i]; }
private:
int data[10];
};
要素をユーザーから非表示にするのはコンテナージョブではありません(data
でもパブリックにすることができます)。要素の読み取りまたは書き込みのどちらを行うかに応じて、要素にアクセスするためのさまざまなメソッドが必要ないため、const
および非constオーバーロードを提供することは、この場合完全に意味があります。
getとカプセル化からの非const参照
多分それは明白ではないかもしれませんが、ゲッターとセッターを提供することがカプセル化をサポートするか、またはその逆をサポートするかどうかは少し物議を醸しています。一般に、この問題は意見に基づいた大規模なものですが、非const参照を返すゲッターにとっては、意見についてはそれほどではありません。彼らはカプセル化を破ります。検討する
struct broken {
void set(int x) {
counter++;
val = x;
}
int& get() { return x; }
int get() const { return x; }
private:
int counter = 0;
int value = 0;
};
名前が示すように、このクラスは壊れています。クライアントは単に参照を取得でき、クラスは値が変更された回数をカウントする機会がありません(set
が示唆するように)。 const以外の参照を返すと、カプセル化に関して、メンバーをパブリックにすることにはほとんど違いがありません。したがって、これは、そのような動作が自然な場合(例:コンテナー)にのみ使用されます。
[〜#〜] ps [〜#〜]
この例では、値ではなくconst T&
が返されることに注意してください。これは、コピーのコストがわからないテンプレートコードの場合は妥当ですが、int
の場合は、int
ではなくconst int&
を返すことで大きなメリットは得られません。わかりやすくするために、テンプレート以外の例を使用しましたが、テンプレートコードの場合は、おそらくconst T&
を返します。
まず、質問を言い換えさせてください。
単にメンバーを公開するのではなく、非const getterをメンバーに持つのはなぜですか?
考えられるいくつかの理由:
非constゲッターは次のようにする必要があると誰が言ったのですか。
_ T& get() { return val; }
_
?それは次のようなものになるでしょう:
_ T& get() {
if (check_for_something_bad()) {
throw std::runtime_error{"Attempt to mutate val when bad things have happened");
}
return val;
}
However, as @BenVoigt suggests, it is more appropriate to wait until the caller actually tries to mutate the value through the reference before spewing an error.
_
一部の組織では、コーディング標準を実施しています。これらのコーディング標準は、過度に防御的である可能性のある人々によって作成される場合があります。だから、あなたは次のようなものを見るかもしれません:
クラスが "plain old data" type でない限り、データメンバーをパブリックにすることはできません。必要に応じて、このような非公開メンバーにはゲッターメソッドを使用できます。
そして、特定のクラスが非constアクセスのみを許可することが理にかなっているとしても、それは起こりません。
val
はありませんか?クラスのインスタンスに実際にval
がexistsする例を示しました。しかし、実際には-必要はありません! get()
メソッドは、プロキシオブジェクトの一種を返す可能性があります。これにより、代入、変更などが何らかの計算(データベースへのデータの格納や取得など)を実行します。
ここで、上記の項目1または3を読むと、「しかし、私の_struct S
_doeshave val
!」または「私のget()
は興味深いことを何もしません!」 -そうですね、そうです。しかし、将来この動作を変更したい場合があります。 get()
がない場合、クラスのすべてのユーザーがコードを変更する必要があります。 get()
を使用すると、_struct S
_の実装を変更するだけで済みます。
さて、私はこの種のデザインアプローチアプローチについては主張しませんが、一部のプログラマーは主張しています。
get()
は、変更が許可されている非constオブジェクトから呼び出すことができます。
_S r(0);
r.get() = 1;
_
r
constをconst S r(0)
として作成すると、行r.get() = 1
はコンパイルされなくなり、値を取得することさえできなくなります。そのため、constバージョンconst T& get() const
少なくともconstオブジェクトの値を取得できるようにします。これにより、次のことが可能になります。
_const S r(0)
int val = r.get()
_
メンバー関数のconstバージョンは、呼び出しが行われたオブジェクトのconstnessプロパティと一致しようとします。つまり、オブジェクトがconstで不変であり、メンバー関数が参照を返す場合、それは反映される場合がありますconstness呼び出し元のconst参照を返すことで、オブジェクトの不変性プロパティを保持します。
S
の目的によって異なります。ある種の薄いラッパーである場合は、ユーザーが基になる値に直接アクセスできるようにするのが適切な場合があります。
実際の例の1つは std::reference_wrapper
。
いいえ。ゲッターが次のように単にメンバーへの非const参照を返す場合:
_private:
Object m_member;
public:
Object &getMember() {
return m_member;
}
_
次に、_m_member
_を代わりにパブリックにする必要があり、アクセサは必要ありません。このメンバーをプライベートにしてから、allへのアクセスを許可するアクセサーを作成することはまったく意味がありません。
getMember()
を呼び出すと、結果の参照をポインター/参照に格納できます。その後、_m_member
_を使用して必要なことをすべて実行できます。外側のクラスはそれについて何も認識しません。 _m_member
_が公開されている場合と同じです。
getMember()
が追加のタスクを実行する場合(たとえば、単に_m_member
_を返すだけでなく、遅延して構築する場合)、getMember()
が役立つことに注意してください。
_Object &getMember() {
if (!m_member) m_member = new Object;
return *m_member;
}
_
質問の実際的な側面も指摘したいと思います。たとえば、オブジェクトBar
を別のオブジェクトFoo
に集約し、doSomething
を読み取り専用で使用する定数メソッドBar
を作成するとします。
class Foo
{
Bar bar_;
public:
void doSomething() const
{
//bar_.get();
}
}
問題があります! Foo
オブジェクトを変更しないことが確実であり、これをconst
修飾子でマークする必要がありますが、get
はconst
。つまり、将来に向けて独自の問題を作成し、明確で高品質なコードの記述を複雑にします。ただし、本当に必要な場合は、get
に2つのバージョンを実装できます。
class Bar
{
int data = 1;
public:
int& get() { return data; };
const int& get() const { return data; };
};
このような状況は、定数修飾子が無視されるコードがすでにある場合に発生する可能性があるため、このような状況は一般的です。
foo(int* a); // Just get a, don`t modified
foo(&bar_.get()); // need non constant get()