tlov

Xcode, C++ 이동 생성자 호출 안될 때 본문

게임개발

Xcode, C++ 이동 생성자 호출 안될 때

nowitzki 2023. 9. 10. 22:36

최근 '이것이 C++이다.'를 공부하고 있는데 '4장 복사 생성자와 임시 객체' 파트에서 아래 소개하는 코드의 결과가 책과 달라 구글을 통해 여러 블로그, 유튜브를 보면서 해결책을 찾아서 저와 같은 고민으로 힘든 분들을 위해 제 생각도 정리할겸 글을 써봅니다. 저는 맥 OS에서 Xcode를 사용하여 코딩하였습니다.

 

* 틀린 부분이 있다면 알려주세요.

 

#include <iostream>
using namespace std;

class CTestData{
public :
    CTestData() {
        cout<<"CTestData()"<<endl;
    }

    ~CTestData(){
        cout<<"~CTestData() : "<<endl;
    }

    CTestData(CTestData &rhs)
    : m_nData(rhs.m_nData)
    {
        cout<<"CTestData(CTestData &)"<<endl;
    }

    CTestData(CTestData &&rhs)
    : m_nData(rhs.m_nData)
    {
        cout<<"CTestData(CTestData &&)"<<endl;
    }
    
    CTestData& operator=(const CTestData &) = default;

   int GetData() const {return m_nData;}
   void SetData(int nParam){m_nData = nParam;}

private :
    int m_nData = 0;
};

CTestData TestFunc(int nParam){
    cout<<"**TestFunc() Begin***"<<endl;
    CTestData a;
    a.SetData(nParam);
    cout<<"**TestFunc() End***"<<endl;
    return a;
}

int main(){
    CTestData b;
    cout<<"*Before*****"<<endl;
    b = TestFunc(20);
    cout<<"*After*****"<<endl;
    CTestData c(b);

    return 0;
}
CTestData()
*Before*****
**TestFunc() Begin***
CTestData()
**TestFunc() End***
CTestData(CTestData &&)
~CTestData()
~CTestData()
*After*****
CTestData(CTestData &)
~CTestData()
~CTestData()

책에서는 위의 실행결과가 나온다고 적혀있습니다. 하지만, 제가 실행시켰을 경우에는 아래 실행결과가 나왔습니다.

CTestData()
*Before*****
**TestFunc() Begin***
CTestData()
**TestFunc() End***
~CTestData()
*After*****
CTestData(CTestData &)
~CTestData()
~CTestData()

왜 이동 생성자가 호출되지 않았을까요?

일단, 이동 생성자가 호출되는 위치부터 알아봅시다. 책에서는 TestFunc에서 return a;를 하는 순간 컴파일러에서 임시 객체를 만들고 그와 동시에 임시 객체의 이동 생성자를 호출한다고 말합니다. 저는 처음에 b = TestFunc(20); 코드에서 임시 객체가 b 객체로 대입될 때 이동 생성자가 호출되는 것으로 착각했었습니다. 하지만, 생각해보면 당연히 말이 안되는 게 이미 b 객체는 위에서 선언하였으니 이미 이 객체는 디폴트 생성자를 호출하여 생성되었기 때문입니다.

여튼, 그럼 이제 이동 생성자가 호출되는 위치를 알았으니 왜 생략되었는지를 알아야겠죠. 이는 구글 검색을 통해 알게되었는데, '복사 생략 copy elision'이 일어나기 때문입니다. 이 복사 생략은 컴파일러에서 알아서 하는 최적화 기능입니다.

위의 코드에서 TestFunc 함수를 보면 내부에서 객체를 만들고 그 객체를 return하고 있지요? 원래라면 임시 객체를 생성해서 복사 혹은 이동 생성자를 호출하는 것이 맞지만, 컴파일러가 판단하기에 필요없는 연산이라 생각하여 NRVO(Named Retrun Value Optimization) 복사 생략을 합니다. 그러면, 임시 객체를 만들지 않고 그냥 a 객체 자체를 return 해버립니다.

그래서 복사 혹은 이동 생성자가 호출되지 않았던 것입니다! 그리고 이러한 복사 생략 최적화를 끄는 방법이 있습니다.

 

다음처럼 main 함수가 위치한 cpp 파일을 더블클릭하여 `-fno-elide-constructors` 문구를 추가하기만 하면 됩니다. 혹은 b = TestFunc(20); 코드를 `b = std::move(TestFunc(20));` 으로 바꿔줘도 똑같이 이동 생성자가 호출됩니다. std::move는 의도적으로 이동 생성자를 호출시켜주는 함수라고 생각하면 됩니다.

여튼 이로써 책과 같은 실행결과를 만들어낼 수 있었습니다. 그리고 복사 생성자와 이동 생성자 관련하여 이 유튜버 영상에서 도움을 많이 받았습니다.