テクニックとしてconst-correctnessを受け入れると仮定すると、簡潔さよりもコンパイラーでチェックされたconst-correctnessを好むことを意味すると思います。したがって、コンパイラに2つのことをチェックさせます。
- 「this」が非定数の場合、返すポインタの参照先は、呼び出し元が安全に変更できます。
- 「this」がconstの場合、どのような作業を行っても「this」に対して非const操作は実行されません。
constバージョンがnon-constを呼び出す場合、(2)は得られません。非constバージョンがconstバージョンを呼び出し、結果をconst_castsした場合、(1)は得られません。たとえば、Bar
が実際char
にであり、作成したコードが(場合によっては)文字列リテラルを返すことになります。これはコンパイルされますが(-Wwrite-stringsは警告を表示しません)、呼び出し元は文字列リテラルへの非constポインターになります。これは、「コンパイラでチェックされたconst-correctnessを好む」と矛盾します。
両方がヘルパーメンバー関数Bar *getBar() const
を呼び出す場合、(1)と(2)の両方を取得します。しかし、そのヘルパー関数を記述できるのであれば、const Fooから返されたバーを変更してもまったく問題がないのに、そもそもconstバージョンとnon-constバージョンをいじくりまわしているのはなぜですか?場合によっては、実装の詳細によって、必要なアクセサーが1つだけであっても、2つのアクセサーを使用してインターフェースを実装していることがあります。そうしないと、ヘルパーを記述できないか、2つの関数を1つのヘルパーだけで置き換えることができます。
コードサイズが問題にならない限り、(1)と(2)の両方を実現する最善の方法は、コンパイラに実際に両方のケースを考慮させることだと思います。
struct Bar { int a; };
struct Foo {
Bar *bar() { return getBar<Bar>(this); }
const Bar *bar() const { return getBar<const Bar>(this); }
Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
Bar *bar3() const { return getBar<const Bar>(this); } // likewise
private:
template <typename B, typename F>
static B *getBar(F *self) {
// non-trivial code, which can safely call other functions with
// const/non-const overloads, and we don't have to manually figure out
// whether it's safe to const_cast the result.
return &self->myBar;
}
Bar myBar;
};
operator[]
オブジェクトが所有する配列にアクセスするようなコードが些細なものである場合は、コードを複製するだけです。ある時点で、上記の関数テンプレートは複製よりもコーディングの労力が少なく、その時点でテンプレートを使用します。
const_castアプローチは、巧妙で一見標準的ですが、コンパイラーによってチェックされたconst-correctnessよりも簡潔さを選択するため、役に立たないと思います。メソッド内のコードが簡単な場合は、複製できます。些細なことではない場合、const_castが実際に有効であることを確認するのはあなたやコードメンテナにとって簡単ではありません。