2010-10-12

暗黙のムーブはやっぱやめよう

Implicit Move Must Go « C++Next

暗黙のムーブは深刻な互換性の問題を引き起こすから、削除しようとのこと。

問題点としては、C++03のコードは、暗黙のムーブを考慮せずに書かれてきた。もちろん、暗黙のムーブなんてものは存在しなかったのだから、これは当然である。しかし、以下のような場合、

// well-formedなC++03のコード
// C++0xではill-formed
class C
{
public:
    C() : v(5) { }
    ~C() { std::cout << v[0] << std::endl ; }
private :
    std::vector<int> v ;
} ;

int main()
{
    C a = C() ;
}

C()によって生成される、prvalueの一時オブジェクトは、暗黙のムーブによって、ムーブされる。std::vectorは、ムーブ可能だからだ。しかし、std::vectorをムーブしてしまうと、もはや要素にアクセスすることはできない。しかし、C()によって生成される一時オブジェクトのデストラクターが走る際、メンバーであるvの要素にアクセスしている。これはエラーとなる。

このコードは、C++03ではwell-formedだが、C++0xではill-formedとなる、互換性のないコードである。

やはり、暗黙のムーブは、あまりにも登場が遅すぎたと言わざるをえない。これに対して、いくつか解決方法が考えられた。たとえば、ユーザー定義のデストラクターがあるばあい、暗黙のムーブの生成をしないようにしようというものだ。しかし、その場合でも、やはり互換性の問題がある。ついには、privateなメンバーを持つクラスは暗黙のムーブを生成しないようにしようなどという意見まででてきた。しかし、それでもなお、この問題は解決しない。

というわけで、暗黙のムーブは、残念ながら、ドラフトから取り除くべきだというのが、Daveさんの意見のようだ。ただ、デフォルトのムーブの動作は、なかなか便利なので、例えば = default ;によって、明示的にデフォルト化された場合は、暗黙のムーブを明示的に生成するような機能を残すのもいいのかもしれない、とも言っている。

いずれにせよ、互換性は言語の進化への大いなる障害だと言わざるをえない。言語の普及のためには、どうしても必要なのだが。

7 comments:

Anonymous said...

リンク先見る限り、暗黙moveじゃなくてstd::removeがクソなだけに見えますね。
ムーブされた可能性のある要素に触ったらそりゃダメでしょうよ。というか明示moveでも信用ならなくなっちゃう。

std::removeした後で第二引数以降の要素に触れる保証さえ潰せば、ユーザー定義デストラクタでの抑制だけで上手く行きそう。

江添亮 said...

C++03では、暗黙のムーブなどというものがなかったから、メンバー変数が無効な状態になっているかもしれないなんてことは考慮する必要がなかったのです。

暗黙のムーブは、規格に入れるべきなのですが、互換性を考えると、無理なのです。

江添亮 said...

std::removeの実装には問題がありません。
というのも、ムーブが使える場合は、当然ムーブすべきなのですから。

Anonymous said...

いやあ、std::removeの場合は「ムーブが使える場合」なんかじゃないでしょう。
std::removeはコンテナの中身をただ並べ替える(というと語弊があるけど)だけで、何かを消す機能はありません。
ムーブ後の抜け殻を残していくことの方がおかしい。
std::moveした後のオブジェクトに触らせといて壊した壊したと騒いでるのと同じですよ。
ムーブが明示だろうと暗黙だろうと関係ない。
現状の仕様ではstd::removeはコピーで実装されるべきです。ゴミへのアクセスをも保証したいならね。

江添亮 said...

std::removeとは、ただ並べ替えるだけです。
その際に、C++0xでは、ムーブすることが可能というだけです。
ムーブ後のオブジェクトは、ムーブされた後も、代入や破棄ができるべきなのです。ムーブされた後のオブジェクトを操作しようとするとエラーになるというのは、ムーブを正しく実装していないクラスなのです。

C++03では、暗黙のムーブがなかったために、それでもよかったのですが、C++0xでは、暗黙のムーブがあるために、互換性の問題が起こります。
だから、暗黙のムーブは廃止したほうがよいという結論なのです。

Anonymous said...

> C++0xではill-formed

問題はクラスの不変条件が壊れる可能性が発生してしまうことで、 well-formed, ill-formed という問題ではなく、挙げられたコードは C++03 でも C++0x でも well-formed なはずです。

Anonymous said...

ごめんなさい、よくわからない。
ムーブされた後で破棄ができなきゃならないのはよくわかるけど、代入だのその他諸々の操作だのははどうして必要なんですか?
ムーブってそもそも「このオブジェクトもういらないからぶっ壊していいよね」って発想でしょう?ぶっ壊した後で破棄(デストラクタ呼び出し)以上のことが出来る必要があるんですか?

要するに、
Hoge a;
Hoge b = std::move(a);
の後でも、aに対してあらゆる操作を保証しないと「ムーブを正しく実装していないクラス」ということになってしまうということですよね。
ムーブのハードル上がりすぎですよね。std::removeのためだけに。