アクセサ(setterやgetter)を実装する際に,Publicなconst,非const関数の両者実装したい場合がある.
直観的にはconst,非const関数をそれぞれ別名で以下のように実装するだろう.
public:
const Hoge* ConstGetter() const { /* 実際に返す処理(getter)を記述する */ }
Hoge* NonConstGetter() { /* 実際に返す処理(getter)を記述する */ }
関数名をconstとnon-constで使い分けるのは,面倒です...
正直なところ関数名が別々であるとわかりにくい.単純に上のようにConst…とNonConst…としてもいいが,関数名が長くなる.場合によっては,似たような関数が増えていくと命名に悩むこともあるだろう.
利用者はアクセサのconst,非constをあまり意識せずに使いたいというのが本音である.(クラスそのもののconstは意識したほうが良いが...)
この問題を解決するには,C++の仕様「const,非constメンバ関数はオーバライドが可能である」をいうことを利用すると比較的簡便に実装できる.
class Foo
{
public:
Foo(){ }
const int * GetValue() const { return this->value_; }
int * GetValue() { return this->value_; }
private:
int* value_ = nullptr;
};
int main()
{
{ // const
const Foo foo;
auto ptr = foo.GetValue(); // const int * GetValue() const が呼ばれる
}
{ // non-const
Foo foo;
auto foo.GetValue(); // int * GetValue() が呼ばれる
}
return 0;
}
このように実装することで,利用者はクラスそのもののconst,非constを意識するだけで,自動的にアクセサconstの有無を識別してくれる.
上記に示した例は極めて簡単な例題であり,実用上はメンバ変数に直接返す関数はあまり使用されない.Getter自体が何らかの処理をし,値もしくはクラスを返すという処理をするだろう.Getterの中身の複雑になるもしくは記述量が増えると,同じ実装を記述するのはDRY原則に反する.
効率的なconst,非constのアクセサ関数の実装
const,非constのアクセサ関数のベストプラクティスとして,次のような実装である(と思う).
public:
Hoge* GetHoge() { return GetHogePrivate(); }
const Hoge* GetHoge() const { return GetHogePrivate(); }
private:
Hoge GetHogePrivate() const { /* 実際に返す処理(getter)を記述する */ }
PrivateなGetter (“Hoge GetHogePrivate() const”)を用意し,PublicなGetterがconst (“const Hoge* GetHoge() const”),非const (“Hoge* GetHoge()”)に変換しreturnするという動作になる.Getter自体を変更する際には,PrivateなGetterのみを変更することで対応できる.
もし,PrivateなGetterを用意せず, const,非constのGetterに対し同じ実装をしていた場合,実装時にコピペミスなどの可能性があり,危険である.
Const,non-constのGetterはPublic宣言し,
実装はPrivateなGetterを使えばOK!
メリット
- const,非const関数に対し,それぞれ実装を記述する必要がない.(DRY原則)
- 実装が比較的わかりやすく,記述量も少ない
その他の実装方法
上述した実装方法以外のDRY原則に則ったconst,非constメンバ関数の実装方法を紹介する.
const_castを使用する方法
const_castを利用する最も単純な発想に基づいたもの.以下のような実装になるだろう.関数名を別名にしているのは,同名の場合うまくいかないためである(関数が再帰的に呼び出される).
public:
const Hoge* GetHogeConst() const { /* 実際に返す処理(getter)を記述する */ }
Hoge* GetHogeNonConst() { return const_cast<Hoge*>(GetHogeConst()); }
デメリット
- 同名のアクセサが使用できないため,関数を別名にしないといけない.
- const_castを実装すること自体が面倒->タイポする可能性あり.
- const_castの処理時間がかかるのでは?(実測していないので不明)
1.の「別名の関数を用意しないといけない問題」に関しては,下記サイトで代替方法を記載している.発想は素晴らしいがやはり実装自体が面倒な気がする.
結論
Const,非Constのアクセサ(Getter)は次のようなコードを使用しよう.
public:
Hoge* GetHoge() { return GetHogePrivate(); }
const Hoge* GetHoge() const { return GetHogePrivate(); }
private:
Hoge GetHogePrivate() const { /* 実際に返す処理(getter)を記述する */ }
コメント