Visual Studio 2010 공식 팀 블로그 @vsts2010

Posted by 흥배

STL RValue Reference

 

앞선 글에서 C++0x STL에는 RValue Reference가 적용되었다고 말했습니다.

이 덕분에 기존의 코드를 수정하지 않고 C++0x의 컴파일러로 빌드만 해도 프로그램의 성능이 좋아집니다.

 

 

RValue Reference가 적용된 C++0x STL vector

VC++ 10 vector쪽 소스 코드를 보면 이전의 vector에는 없던 코드가 있습니다.

< Code 1. STL vector의 소스 중 일부 >

……………………

#if _HAS_RVALUE_REFERENCES

 

                  vector(_Myt&& _Right)

                                   : _Mybase(_Right._Alval)

                  {                // construct by moving _Right

                     _Assign_rv(_STD forward<_Myt>(_Right));

                  }

………

 

<Code 1>의 내용을 보면 바로 아시겠죠? Move 생성자가 정의 되어 있습니다.

< Code 2. >

vector<int> foo()

{

        vector<int> v;

        v.push_back(1);

        v.push_back(2);

        return v;

}

 

int main()

{

        vector<int> v1 = foo();

        cout << v1[0] << endl;

        return 0;

}

 

<Code 2> VC++9(visual Studio 2008) VC++ 10(Visual Studio 2010 CTP)에서 컴파일 해서 실행하면 vector의 생성자에서 서로 다른 부분을 호출하는 것을 볼 수 있습니다.

 

VC++ 9에서 <Code 2> 디버깅

VC++ 9에서 디버깅을 해보면 다음의 위치에서 브레이크 포인터가 걸립니다.

 

위 코드를 보면 복사 생성자에서 받은 vector의 크기를 비교하여 현재 공간이 인자로 받은 vector보다 작으면 재할당을 하고, vector에 있는 모든 요소를 복사합니다.

 

VC++ 10에서 <Code 2> 디버깅

 

보시는 바와 같이 VC++ 10에서는 Move 생성자가 호출됩니다. 그리고 요소를 복사하지 않고 메모리 상의 이동을 합니다.

크기가 작은 vector라면 성능상의 차이는 별 의미가 없겠지만 크기가 큰 vector라면 무시하지 못할 차이가 납니다.

또한 vector에 할당된 공간을 다 사용한 상태에서 새로운 요소를 삽입하면( vector.push_bask() ) 기존에는 새로운 공간을 할당 후 저장하고 있던 모든 요소를 복사를 하지만 C++0x에서는 Move Semantics에 의해 메모리 상의 이동을 합니다.

 

STL string

 너무 당연하지만 string 클래스도 RValue Reference가 잘 적용되어 있습니다.

< Code 3. string 결합>

string msg1(“Error”);

string msg2(“- Network”);

string msg3(“: Accept”);

 

string Msg = msg1 + “ “ + msg2 + “ “ + msg3;

 

<Code 3>에 만약 RValue Reference가 적용되지 않았다면 operator+()가 호출될 때마다 임시 문자열이 생성되어 동정 메모리 할당과 복사가 일어나지만 RValue Reference을 적용하면 불 필요한 동적 메모리 할당과 복사 처리를 제거할 수 있습니다.

 

 

std::move()를 사용할 때 주의할 점

 std::move()를 사용하면 Move Semantics에 의해 의도하지 않은 버그가 발생할 수 있습니다.

 < Code 4. >

int main()

{

           vector<int> v1;

           v1.push_back(10);

           v1.push_back(12);

 

           vector<int> v2 = std::move(v1);

          

           cout << v1.size() << endl;

           cout << v2.size() << endl;

 

           return 0;

}

 

< 결과 >


<Code 4>에서 v1 v2에서 Move 생성자를 사용하기 위해서 std::move()를 사용했습니다.

std::move는 메모리 이동을 한다고 했습니다. 그래서

          vector<int> v2 = std::move(v1);

후에는 v1의 크기는 0이 됩니다.

 

 

이것으로 RValue Reference에서의 Move Semantics에 대한 이야기는 일단락합니다.

제가 너무 복잡하지 않게 설명하기 위해 세세한 부분에 대한 것은 설명하지 않았으니 좀 더 정확하게 알고 싶은 분들은

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

의 글을 꼭 보시기 바랍니다.

 

 

다음에는 Perfect Forwarding에 대하여 이야기로 우측참조에 대한 글을 끝맺겠습니다.

저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

  1. 잘보았습니다. ^^

  2. 잘 봤습니다.

    그런데 메모리상의 이동이라고 표현하시는데...

    실제론 그냥 개체 포인터의 복사 및 소스포인터 nullify가 전부 아닌가요?

    메모리의 이동이라고 표현하니 웬지 물리적 페이지가 프로세스 내의 가상주소공간에서 이동하는 것처럼 들려서 좀 헷갈리네요.

    그리고 code 3 에선 operator+() 호출시에 임시개체가 생성되고 memcpy, 심지어 문자열이 길면 malloc 도 호출됩니다.
    단지 2번째 operator+() 호출부터는 각 임시개체간에 데이터 복사 대신 포인터 대입 및 소스포인터 nullify 가 일어날 뿐이죠.
    물론 그래봤자 2번째 피연산자 역시 길이가 길다면 임시개체에 대하여 malloc 및 memcpy 호출이 발생합니다.

    그리고 연산이 다 끝난 후에도 임시개체의 포인터가 Msg 에 직접 대입된 후 임시개체의 버퍼 포인터가 nullify 될 뿐이구요.
    물론 malloc 와 memcpy 가 2번 호출될 것이 1번으로 줄긴 했지만서두...

    아무튼 Move Semantics 이란게 뭔가 했더니...
    난 또 무슨 page remapping 쯤 되는줄 알았는데...
    머리가 간지럽다고 해서 손바닥을 긁어 머리에 갖다대는 수준의 병맛스런 개념이었군요.

    에효... 언제나 c++ 이 제정신으로 돌아오려는지...

  3. 불필요한 작업을 없애는 정도네요...