そもそもgetter/setterめんどいだけじゃねとか設計的にいらなくね、とかはなしで。
Move Semanticsの恩恵に与ることができるsetter
すでに知られていることだが、setterと言うのはmoveとcopy両方を集約して書いても構わない(事が多い)。
class Foo {
std::string value;
public:
Foo() = default;
~Foo() = default;
void setValue(std::string value_) {
value = std::move(value_); // value_は自明にmoveしていい
}
};
int main(){
Foo f;
std::string x = "test1";
f.setValue(x); // 引数にcopyされて渡る.
x = "test2";
f.setValue(std::move(x)); // 引数にmoveされて渡る.
return 0;
}
この場合sizeof(std::string)
が大きい時にはこの手を使うのは悪手ってことになるけど、だいたいポインタ2つくらいしか使ってないだろうから大したことない。
rvalue reference for *thisを使わないといけないgetter
一方getterはそう簡単には行かない。以下のコードを見てほしい。
#include <cassert>
#include <iostream>
#include <string>
enum class GetterMode {
LVALUE,
RVALUE,
CONST_LVALUE,
};
GetterMode mode;
class Foo {
std::string value;
public:
Foo(std::string value): value(value) {}
~Foo() = default;
std::string& getValue() { mode = GetterMode::LVALUE; return value; }
const std::string& getValue() const { mode = GetterMode::CONST_LVALUE; return value; }
};
int main() {
Foo x("xxx");
const Foo y("yyy");
x.getValue(); // 通常のgetValueが呼ばれる
assert(mode == GetterMode::LVALUE);
y.getValue(); // const lvalue版が呼ばれる
assert(mode == GetterMode::CONST_LVALUE);
Foo("zzz").getValue(); // lvaue版が呼ばれる
assert(mode == GetterMode::LVALUE);
std::string p = Foo("zzz").getValue(); // lvalue版が呼ばれる
assert(mode == GetterMode::LVALUE);
std::string r = std::move(Foo("zzz").getValue()); // lvalue版が呼ばれる
assert(mode == GetterMode::LVALUE);
x.getValue() = "aaa"; // getterなのに代入できちゃう
}
見ての通り多くの問題点がある。
- lvalueの参照を返すgetterと言うのはその参照を使って誰かに値を書き換えられる。つまり実のところgetterを作ったつもりがsetterを作ってしまう。
operator*()
とかはそれでいいんだけどgetterとしてはだめ。 -
Foo("zoo").getValue()
のようなrvalueに対してgetterを読んだ場合も出てくるものがstd::string&
なので、そのまま代入するとコピーコンストラクタが呼ばれてしまう。それを避けるために全体をmoveしてstd::move(Foo("zoo").getValue())
のようにしてやらないとだめで直観的でない
こういう時は次のように*thisに対するオーバーロードを利用する。
#include <cassert>
#include <iostream>
#include <string>
enum class GetterMode {
LVALUE,
RVALUE,
CONST_LVALUE,
};
GetterMode mode;
class Foo {
std::string value;
public:
explicit Foo(std::string value): value(value) {}
std::string&& getValue() && { mode = GetterMode::RVALUE; return std::move(value); }
const std::string& getValue() const& { mode = GetterMode::CONST_LVALUE; return value; }
// volatile修飾は省略
};
int main() {
Foo x("xxx");
const Foo y("yyy");
x.getValue(); // const lvalue版呼ばれる
assert(mode == GetterMode::CONST_LVALUE);
y.getValue(); // const lvalue版が呼ばれる
assert(mode == GetterMode::CONST_LVALUE);
Foo("zzz").getValue(); // rvalue版が呼ばれる
assert(mode == GetterMode::RVALUE);
std::string p = Foo("zzz").getValue(); // rvalue版が呼ばれる
assert(mode == GetterMode::RVALUE);
std::string q = std::move(x).getValue(); // rvalue版が呼ばれる
assert(mode == GetterMode::RVALUE);
// x.getValue() = "aaa"; // エラー!
static_assert(std::is_same<decltype(x.getValue()), const std::string&>::value, "x.getValue() should return const lvalue reference");
static_assert(std::is_same<decltype(std::move(x).getValue()), std::string&&>::value, "std::move(x).getValue() should return rvalue reference");
static_assert(std::is_same<decltype(Foo("zoo").getValue()), std::string&&>::value, "Foo(\"zoo\").getValue() should return rvalue reference");
return 0;
}
これで無事にlvalueのgetterはconst lvalue referenceに、rvalueのgetterはrvalue referenceになった。ここまでするの面倒なんでメンバアクセスfoo.value
で済むなら済ませたほうがいい気がします。
注意点
まずひとつ、普通のconstメソッドのgetValueの方にもconst&
と書いて このメソッドはlvalue this
専用です ということを明示しないといけない。
あと、rvalue reference to *thisと言うのはgccではすごく最近実装された機能です。
http://gcc.gnu.org/projects/cxx0x.html
Language Feature C++11 Proposal Available in GCC? Rvalue references for *this N2439 GCC 4.8.1
clangは早かったみたいですが。
http://clang.llvm.org/cxx_status.html
Language Feature C++11 Proposal Available in Clang? Rvalue references for *this N2439 Clang 2.9
なので、gcc使ってるとまだまだ使えない事が多いと思うので注意してください。
追記 (2014-06-10)
添付のソースが嘘ソースだったので修正