読者です 読者をやめる 読者になる 読者になる

C++0xのムーブセマンティクス(値のつなぎ替え)と右辺値参照

Faith and Braveさんの記事を参考に、C++Builder 2009で試してみた。

//---------------------------------------------------------------------------
#include <iostream>
#include <utility>
#pragma hdrstop

#include <tchar.h>
//---------------------------------------------------------------------------
class CFoo
{
public:
    CFoo() :
        m_Value(0)
    {
    }

    CFoo(const CFoo& foo) :
        m_Value(foo.m_Value)
    {
        std::cout << "Copy constructor" << std::endl;
    }

    CFoo(CFoo&& foo) :
        m_Value(foo.m_Value)
    {
        std::cout << "Move constructor" << std::endl;
        foo.m_Value = 0;
    }

    CFoo& operator=(CFoo&& foo)
    {
        std::cout << "operator=(Move)" << std::endl;

        std::swap(m_Value, foo.m_Value);
        return *this;
    }
public:
    int m_Value;
};

CFoo FooFunc()
{
    CFoo foo;
    foo.m_Value = 2000;
    return foo;
}

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
    CFoo foo1;
    foo1.m_Value = 1000;

    // 値のコピー
    std::cout << "CFoo lfoo = foo1;" << std::endl;
    CFoo lfoo = foo1;
    std::cout << "foo1.m_Value = " << foo1.m_Value << std::endl
              << "lfoo.m_Value = " << lfoo.m_Value << std::endl
              << std::endl;

    // 値の転送(転送元は破壊される)
    std::cout << "CFoo rfoo1 = std::move(foo1);" << std::endl;
    CFoo rfoo1 = std::move(foo1);
    std::cout << "foo1.m_Value = " << foo1.m_Value << std::endl
              << "rfoo1.m_Value = " << rfoo1.m_Value << std::endl
              << std::endl;

    // 戻り値(一時オブジェクト)の参照(1)
    std::cout << "CFoo&& rfoo2 = FooFunc();" << std::endl;
    CFoo&& rfoo2 = FooFunc();
    std::cout << "rfoo2.m_Value = " << rfoo2.m_Value << std::endl
              << std::endl;

    // 戻り値(一時オブジェクト)の参照(2)
    std::cout << "CFoo rfoo3 = FooFunc();" << std::endl;
    CFoo rfoo3 = FooFunc();
    std::cout << "rfoo3.m_Value = " << rfoo3.m_Value << std::endl;

    return 0;
}
//---------------------------------------------------------------------------

こいつをコンパイルしてみる。

D:\home\A7M\BCBTEST\rvalue_ref>bcc32 File1.cpp
CodeGear C++ 6.10 for Win32 Copyright (c) 1993-2008 CodeGear
File1.cpp:
Turbo Incremental Link 5.96 Copyright (c) 1997-2008 CodeGear

D:\home\A7M\BCBTEST\rvalue_ref>file1
CFoo lfoo = foo1;
Copy constructor
foo1.m_Value = 1000
lfoo.m_Value = 1000

CFoo rfoo1 = std::move(foo1);
Move constructor
foo1.m_Value = 0
rfoo1.m_Value = 1000

CFoo&& rfoo2 = FooFunc();
Move constructor
rfoo2.m_Value = 2000

CFoo rfoo3 = FooFunc();
Move constructor
rfoo3.m_Value = 2000

D:\home\A7M\BCBTEST\rvalue_ref>

「ムーブセマンティクス」とは、関数の戻り値のような一時オブジェクトは破棄されることがあらかじめ判っているから、ならば、その一時オブジェクトが握っているリソースを受け側のリソースとして「つなぎ替える」ことでリソースを複写をせずに、受け手へ高速に値を渡す手法。「右辺値参照」と「ムーブコンストラクタ」はムーブセマンティクスを実装する為にC++0xで導入された。「右辺値」とは、関数の戻り値のように変数名とは結び付いていない(=代入出来ない)オブジェクト。*1「右辺値参照」とは、値を「右辺値」として参照する。今までの「参照」は狭義の「左辺値参照」となる。*2「ムーブコンストラクタ」は右辺値が渡されたときのコンストラクタと理解していいのかな?ムーブコンストラクタとコピーコンストラクタの両方がある場合、ムーブコンストラクタが優先される。

やりたいことは何となく理解したつもりだけど、簡潔に説明するのは難しそうだ。それに、CFoo::operator=(CFoo&& foo)が呼び出されるシチュエーションがよく判らないけど、派生クラスとかのムーブコンストラクタ内で呼ばれる場合があるのかな?

*1:厳密には代入可能な値(変数のこと?)が「左辺値」で、それ以外のものが「右辺値」。ようやく左辺値と右辺値の違いを理解できた。(゚∀゚)アヒャヒャヒャヒャ

*2:C++0xでの「参照」とは「左辺値参照」と「右辺値参照」の両方の意味となる