C++프로그래밍/모던C++

오른값 참조(rvalue)와 std::move

season97 2024. 10. 2. 20:44
728x90
반응형

※ 개인적인 공부를 위해 포스팅 하는 글입니다.


개념 포스팅 링크 : https://season97.tistory.com/49

 

L- value 와 R - value / move

▶ L - value 와 R - value ㆍ 프로그래밍 언어에서 변수와 값의 메모리 상태, 수명에 관련된 개념. 1. L - value ㆍ 메모리의 특정 주소를 가지며, 그 주소에 저장된 값을 참조할 수 있는 표현. 즉, 변

season97.tistory.com

int a = 3;
3 = a;
a++ = 5;

 

ㆍ ..? 다 뭐 말도안되는짓이다. 저런 임시값들이 다 r-value다.

 

# 복기해보자

void TestKnight_Copy(Knight knight) { knight._hp = 320; }
void TestKnight_LValueRef(Knight& knight) { knight._hp = 320; }
int main()
{
    // 당연하게도 이 코드를 실행해도 원본 객체엔 변환이 없다. 
    Knight k1;
    TestKnight_Copy(k1);

    // 이 버전으로 실행했으면 불필요한 복사가 일어나지 않고, 원본 객체도 수정되었었다.
    TestKnight_LValueRef(k1);
}

ㆍ 기초적인 값전달, 참조전달이다.

 

# R-value 

TestKnight_LValueRef(Knight());

ㆍ 이렇게 한번 매개변수를 넘겨보자

ㆍ 이런 에러 메세지가 나온다..

void TestKnight_ConstLValueRef(const Knight& knight) {  }
int main()
{
   TestKnight_ConstLValueRef(Knight());
}

ㆍ 그래서 이렇게 해보니까 일단 에러가 사라지고 받아주긴 한다... 왜LValue에선 받아주지 않을까?

 

ㆍ 임시 객체라 함은 조금있으면 사라질 애다... 어처피 함수에서 넘겨줘봤자 (애초에 주소가 없으니 주소를 넘겨준다는 말도 어색한거 같다) 사라질 애라 문법적으로 막아준 것

class Knight
{
public:
    void Print() const
    {
        cout << "dd" << endl;    
    }
}
void TestKnight_ConstLValueRef(const Knight& knight) { knight.Print(); }

 

ㆍ 그래서 const로 하면 어처피 고쳐질 일 없으니 문법적으로 허락하고 있는것 이다. 근데 const로 붙히면 내부에서 사용하는 Knight의 함수도 const함수여야만 하는 반쪽짜리 함수 느낌이다.

 

 

# 그래서 모던C++11 에서 나온 R-Value 참조

void TestKnight_RValueRef(Knight&& knight) {}

int main()
{
    TestKnight_RValueRef(Knight());
}

ㆍ 요놈은 사용할때 L-value를 넘겨주면 오류가 난다

 

간만에 어셈블리로 까보자

어셈블리에선 그렇게 큰 차이가 없다. 하지만C++입장에선 큰 차이가 있다.

ㆍ 그냥 임시객체를 만들어주고 넘겨주고있는 모습이다.  

ㆍ어셈블리에선 그저 실질적으로는 원본 자체를 넘긴다는게 보여진다.

 

# 각 함수의 차이를 알아보자

void TestKnight_Copy(Knight knight) {  }			
void TestKnight_LValueRef(Knight& knight) {  }
void TestKnight_ConstLValueRef(const Knight& knight) { }
void TestKnight_RValueRef(Knight&& knight) {} 

//2번쨰줄 : 원본을 넘겨줄태니까 너 맘대로 가지고 놀아
//3번쨰줄 : 내가 원본을 주긴 줄건데 수정해선 안되고 변경해도 안돼. 그냥 읽기만 해
//4번째줄 : 내가 어떤 원본 객체를 넘겨줄거야, 읽고쓰는거도 너 맘대로 해, 심지어
// 함수가 호출된 후에는 난 그 변수를 쓰지않을거니까 너 맘대로해!

ㆍ 즉 R-Value참조는 원본 데이터가 유지되도 않는다는 말이다. 

 

# 그래서 좋은게 뭔데?

ㆍ 만약 기사가 엄청나게 큰 클래스고, 기사 내에서도 (예를들어 펫) 다양한 클래스들이 또 안에 있는 구조라고 생각해보자

  class Knight
{
public:
    Knight()
    {}
    
    Knight(const Knight& knight)
    {
    }
    
    Knight(Knight&& knight) {}
    
    ~Knight()
    {
        if (_pet)
        {
            delete _pet;
        }
    }

    void operator=(const Knight& knight)
    {
        cout << "const Knight& 의 = 오퍼레이터" << endl;
        //_hp = knight._hp;
        //_pet = knight._pet;
         //얕복
        
        //깊복
        _hp = knight._hp;
        if (knight._pet)
        {
            _pet = new Pet(*knight._pet);
        }
    } //나이트 원본을 써돈 되지만... 훼손하면 안된다..? 
    // 이런 경우에 r-value참조를 쓰면 좋겠다

    //이동 대입 연산자
    void operator=(Knight&& knight)
    {
        cout << "opertor= (Knight&&)" << endl;
        _hp = knight._hp;
        _pet = knight._pet;
        knight._pet = nullptr;
        //애당초 이 경우엔 rvalue참조로 임시값을 받는다는걸 명시해 줬기 때문에 얕은 복사를 사용해도 되겠다.
    }
public:
    int _hp = 100;
    Pet* _pet = nullptr;
};
Knight k2;
k2._pet = new Pet();
k2._hp = 1000;

Knight k3;
k3 = static_cast<Knight&&>(k2);
//복사를 하지 않고 이동한다는의미...

ㆍ k2을 이렇게 넘겨줄 수도 있겠다.

ㆍ 복사하지 않고 임시값을 바로 넘겨줄거고, 그 임시값은 너 마음대로 수정해도 돼

 

#근데 캐스팅 연산자를 쓰기보단 std::move를 사용하자! (비슷한 말이다.)

Knight k3;
k3 = std::move(k2);

ㆍ std::move의 원래 이름 후보중 하나는 rvalue_cast였다고 한다...

ㆍ std::move 쓰면 기존값은 소유권을 잃는다

 

 

※ 우리가 사용할땐 생각보다 많진 않지만 C++ 코어 라이브러리에선 차이가 크다고 한다. 내부적으로 임시값을 썼다가.. 하는경우가 많았는데 move가 등장한 이후에는 (cpp 11) 컴파일 속도가 꽤 빨라졌다고 한다 (얕복으로 다 꺼내쓸 수 있기 떄문에)

 

 

 

※ 성능적으로 유용한 경우도 있지만, 이 자체로도 쓸모가 있는 경우가 있다. 

▶ unique_ptr  스마트포인터 포스팅 글에서 더 자세히 포스팅예정

ㆍ 코드 타고들어가보면 복사와 관련된것들이 delete로 삭제되어있다.

 unique_ptr<Knight> uptr = make_unique<Knight>();
 unique_ptr<Knight> uptr2 = move(uptr);

ㆍ 소유권을 넘겨줘서 uptr은 쓰래기가 되고 uptr2가 유효하게 된다.

728x90
반응형

'C++프로그래밍 > 모던C++' 카테고리의 다른 글

람다  (0) 2024.10.03
전달 참조(forwarding reference) [(구)(universal reference)]  (0) 2024.10.02
delete (삭제된 함수)  (0) 2024.10.02
using문과 enum class  (1) 2024.10.02
nullptr  (0) 2024.10.01