15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++11のgetter/setterについて

Last updated at Posted at 2013-11-20

そもそもgetter/setterめんどいだけじゃねとか設計的にいらなくね、とかはなしで。

Move Semanticsの恩恵に与ることができるsetter

すでに知られていることだが、setterと言うのはmoveとcopy両方を集約して書いても構わない(事が多い)。

setter.cpp
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はそう簡単には行かない。以下のコードを見てほしい。

getter.cpp
#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に対するオーバーロードを利用する。

getter2.cpp
#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)

添付のソースが嘘ソースだったので修正

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?