컴퓨터공학/C++

[C++] std::move()

Pyxis 2024. 11. 9. 17:15

[ C++ : std::move() ]

 

자주 사용하진 않지만, C++11에서 핵심적인 변화에 해당.

이 기능덕에 코드의 속도 차이가 나게 되었음.

 

[ l-value vs r-value ]

l-value : 단일식을 넘어서 계속 지속되는 개체

r-value : l-vaule가 아닌 나머지 (임시 값, 열거형, 람다, i++ 등)

 

int a = 3;
3 = a; // [Error : 식이 수정할 수 있는 lvalue여야 합니다.]

(a++) = 5; // [Error : 식이 수정할 수 있는 lvalue여야 합니다.]
// → (a++)은 복사된 임시 값이기 때문

 

void TestKnight_Copy(Knight knight) { }
void TestKnight_LValueRef(Knight& knight) { ]
void TestKnight_ConstLValueRef(const Knight& knight) { }
// void TestKnight_ConstLvalueRef(const Knight& knight) { knight.PrintInfo(); }
// → PrintInfo() const 를 붙여야만 실행됨. read only라는 힌트를 줘야하는 것.
// → Error : 개체에 멤버 함수 ""과 호환되지 않은 형식 한정자가 있습니다.


class Knight
{
public:
	void PrintInfo() const // const가 있어야만 호출가능.    
    {
    
    }
int _hp = 100;
}

int main()
{
	/* ... */
	Knight k1;

	TestKnight_Copy(k1); // OK
	TestKnight_LValueRef(Knight()); // Error : [비 const 참조에 대한 초기 값은 lvalue여야 합니다.]
	TestKnight_LValueRef(k1); // OK
	TestKnight_ConstLValueRef(Knight()); // OK → const로 받아 수정 하지 못하는 r-value 이므로
}

 

인자로 Knight()같은 r-value인 임시값을 받게되면, 복사로만 받아주거나 const &로 받아줘야만 통과가 된다. (&는 에러가 남.)

하지만, 크기가 크다면 const &로 받아줄 수 밖에 없는데, 이 경우 함수내에서 인자로 넘어온 객체의 멤버 함수를 실행할려고하면 함수 시그니처뒤에 const를 붙인 read only 함수만 실행가능하다. (Error : 개체에 멤버 함수 ""과 호환되지 않은 형식 한정자가 있습니다.)

→ const &로 받아줬기 때문에 수정하는 것이 말이 안되므로 문법적으로 또 막아준 것인데, 반쪽짜리로만 사용가능한 것.

 

C++11에서, r-value을 받아주는 참조 형식이 생겼다.

void TestKnight_RValueRef(Knight&& knight) { knight._hp = 20; } // OK


/* ... */
int main()
{
    Knight k1;

    TestKnight_RValueRef(k1); // Error : r-value참조를 l-value에 바인딩할 수 없습니다.
    
    TestKnight_RValueRef(Knight()); // const가 아닌 함수도 실행가능, 수정도 가능해짐.
    
    /* ... */
}

 

인자로 &, const &, &&를 넘겨주는 방식은 어셈블리단위로 보면 주소를 넘겨주는 것은 같다.

r-value로 넘겼다고 하더라도 원본의 수정이 가능해진 것.

하지만, C++ 기준에서 보면 &&는 의미가 꽤 다르다.

 

r-value로 넘긴다고 해서, 임시 객체만 넘겨줄 수 있다는 것은 아님.

 

/* ... */
Knight k1;

TestKnight_RValueRef(static_cast<Knight&&>(k1)); // OK

 

l-value인 k1을 r-value reference인 &&로 캐스팅해서 넘긴다면, 통과가 가능함.

→ 본체가 l-value인 개체를 r-value로 캐스팅해서 넘겨주고, 받은 다음 원본을 수정하는 것이 가능해진 것이다.

 

void TestKnight_LValueRef(Knight& knight) { }

 : 원본을 넘겨줄테니 마음대로 수정하거나 읽어도 돼.

void TestKnight_ConstLValueRef(const Knight& knight) { }

 : 원본을 넘겨주지만 읽기만 해.

void TestKnight_RValueRef(Knight&& knight) { /*...*/ } // 이동 대상

 : 원본 객체를 넘겨줄텐데, 읽고 쓰는 것도 마음대로 할 수 있고, 심지어 호출이 끝난 뒤에 활용하지도 않을테니 멋대로 해도 돼.

원본 데이터가 유지되지 않아도 된다. (이동 대상이 된다.)

 

[ 원본을 유지하지 않아도될 때의 장점 : 깊은 복사와 얕은 복사 ]

깊은 복사를 하는 클래스가 있다고 가정해보자.

Knight의 멤버변수에 있는 Pet*, Inventory* 같은 포인터로된 값들을 매번마다 새롭게 만들어줘야 하므로 복사 비용이 커진다. 

Knight&& knight를 인자로 받아온다면 원본이 훼손되어도 된다는 엄청난 "힌트"를 주게 된다.

→ C++11이전에서 깊은 복사를 선택했을 때의 문제는, 깊은 복사와 얕은 복사 중 하나를 선택하는 것이 불가능 하다는 것.

 

그렇다면 Knight&& knight를 인자로 받아온다면 그 원본을 더 이상 사용하지 않기 때문에 깊은 복사까지 가지 않아도 되고, 상대방의 pet을 바로 써도 된다. 그 다음 상대방의 펫을 nullptr로 밀어줘도 된다.

원본을 날려도 되는 상황이 오면, 그것을 &&로 구분하도록 문법이 되었으므로 얕은 복사를 써도 되게 하는 것.

 

/* ... */

void operator=(Knight&& knight) noexcept
{
	cout << "operator=(Knight&&)" << endl;
    
    // Shallodw Copy
    _hp = knight._hp;
    _pet = knight._pet;
    
    knight._pet = nullptr; 
    // 명시적으로 nullptr로 밀어주지 않으면, 인자로 들어온 객체의 원본의 소멸자가 delete를 호출할 수도 있음.
}

/* ... */

 

pet을 이동 대입 연산자를 통해 얕은 복사를 하고, 명시적으로 기존에 있던 이동 대상의 pet을 nullptr로 밀어주는 이유는, 이동 대상이 나중에 소멸될 때 소멸자에서 delete가 호출될 수 있기 때문이다.

 

[ std::move() ]

k3 = static_cast<Knight&&>(k2);
k3 = std::move(k2);

들어온 인자의 타입을 &&로 캐스팅해주는 것.

 

생각보다 쓸일이 많지는 않다.

코어 라이브러리에서는 임시 값을 만들어 줬다가 복사하는 일이 빈번하게 일어나는데, std::move()가 생긴뒤로 똑같은 코드의 실행 속도차이가 꽤 나게된다. (굳이 깊은 복사를 안해도 되므로)

 

[ unique_ptr ]

포인터와 유사하지만, 딱 하나만 존재하는 포인터

std::unique_ptr<Knight> uptr = std::make_unique<Knight>();
std::unique_tr<Knight> uptr2 = uptr; 
// Error : unique_ptr 특성상, 복사 생성자와 복사 대입 연산자를 delete로 막아놨음.

 

Knight를 관리하는 포인터를 옮기고 싶다면, std::move()를 이용하면 옮겨줄 수 있다.

 

 

 

 

 

 

 

[ Reference ]

Rookiss, Part1. C++ 프로그래밍 입문

 

 

 

 

 

 

'컴퓨터공학 > C++' 카테고리의 다른 글

[C++] noexcept  (0) 2024.11.09
[C++] 캐스팅 4가지  (0) 2024.11.09
[C++] 전방 선언  (0) 2024.11.05
[C++] 템플릿 기초  (0) 2024.11.05
[C++] 타입 변환과 얕은 복사, 깊은 복사  (0) 2024.11.05