[C++] 오른값 참조, 전달 참조

seunghyun·2023년 7월 5일
0

l-value 왼값

  • 단일식을 넘어서 계속 지속되는 개체

r-value 오른값

  • lvalue가 아닌 나머지 (임시 값, 열거형, 람다, i++ 등)
  • 즉, 단일식을 벗어나면 사용 불가능한 아이들
  • 참고로 임시 객체는 스택에 잠시 몸을 담는다~

오른값 참조

오른값 참조

  • 오른값만 참조할 수 있는 참조 타입
  • 오른값 참조라고 해서 꼭 오른값인 것은 아니다!

아마 직접적으로 오른값 참조를 쓸 일은 없겠다만 && 이 무엇인지 알아야 한다

벡터의 이사 방식이 이동이다. 경우에 따라 이동과 복사의 차이가 엄청날 수 있기 때문이다.

class Knight()
{
public:
	// 복사 대입 연산자
    void operator=(const Knight& knight)
    {
    	_hp = knight._hp;
        
        if (knight._pet)
        	_pet = new Pet(*knight._pet); // 깊은 복사
    }
    
    // 이동 생성자
    Knight(Knight&& knight) noexcept
    {
    	_hp = knight._hp;
        _pet = knight._pet;
        knight._pet = nullptr;
    }
    
    // 이동 대입 연산자 ... 그냥 "소유권을 이전했다" 라고 생각해도 좋다. 복사한게 아니라.
	void operator=(Knight&& knight) noexcept
    {
    	_hp = knight._hp;
        _pet = knight._pet;
        knight._pet = nullptr; // 어차피 사라질 애니까 nullptr 로 밀어주기
    }
 
public:
	int _hp = 0;
    Pet* _pet = nullptr;
};

void TestKnight_LValueRef(Knight& knight)
{
	// 원본 넘겨줄테니... 건드려도 됨
}

void TestKnight_ConstLValueRef(const Knight& knight)
{
	// 원본 넘겨줄테니... 건드릴 순 없어
}

void TestKnight_RValueRef(Knight&& knight) // 오른값 참조라는 뜻
{
	// 원본 넘겨줄테니... 더 이상 활용하지 않을테니 맘대로 해!
    // 일회성이니 원본 더 안쓸거니 알아서 해
}

int main()
{	
	Knight k1;
    k1._pet = new Pet();
    
    Knight k2;
    // k2 = static_cast<Knight&&>(k1); // 이동~~~ 츕츕!
    k2 = std::move(k1); // 위 문장과 완전 똑같은 의미이다
    
	TestKnight_ConstLValueRef(Knight()); // 임시객체 넘기기
	
    // TestKnight_RValueRef(Knight());
    TestKnight_RValueRef(static_cast<Knight&&>(k1)); // 오른값 참조
}

전달 참조

보편 참조 (universal reference) 와 똑같은 아이이고 역사가 있는 아이인데 전달 참조라고 부른다. (C++17부터)

오른값 참조랑 비슷하게 생겼다. 근데&& 가 붙는다고 무조건 오른값 참조가 아니다!

⭐️ template, auto 처럼 '형식 연역(type deduction)' 이 등장할 때에만 전달 참조가 발생한다. 왼값을 넣으면 왼값 참조, 오른값을 넣으면 오른값 참조로 동작한다는 특징을 가진다.단 const 를 붙여주면 바로 오른값 참조로 바뀐다.

class Knight
{
public:
	Knight() {cout << "기본 생성자" << endl;}
	Knight(const Knight&) {cout << "복사 생성자" << endl;}
	Knight(Knight&&) noexcept {cout << "이동 생성자" << endl;}
	
};

void Test_RValueRef(Knight&& k) // 오른값만 참조할 수 있다
{ 

}

template<typename T>
void Test_ForwardingRef(T&& param) // 전달 참조 // param 도 결국 왼값이라 복사 생성자가 호출된다
{
	// 왼값 참조라면 복사, 오른값 참조라면 이동
	Test_Copy(std::forward<T>(param));

	/*
	// 왼값 참조라면 복사
	TestCopy(param);
	
	// 오른값 참조라면 이동
	Test_Copy(std::move(param)); // 이렇게 오른값 참조로 다시 바꿔줘야 한다.
	*/
}

int main()
{
	Knight k1; // 왼값
	// Test_RValueRef(k1); // error
	Test_RValueRef(std::move(k1)); // ok // rvalue cast

	Test_ForwardingRef(k1); // &
	Test_ForwardingRef(std::move(k1)); // &&

	auto&& k2 = k1; // &
	auto&& k3 = std::move(k1); // &&

	return 0;
}

전달 참조, 왜 만들었을까?

  • 장점: 입력값에 따라 왼값, 오른값 참조 모두 처리가 가능해진다. 하나하나 함수를 만들지 않아도 된다.
  • 오른값 참조라고 해서 꼭 소유권을 넘겨야만 하는 상황이 있는 것은 아닐 것이므로 활용성을 기대할 수 있다.
int main()
{
	Knight& k4 = k1; // 왼값 참조
	Knight&& k5 = std::move(k1); // 오른값 참조

	// Test_RValueRef(k5); // error // k5 가 단일식을 벗어나도 사용 가능하므로
	Test_RValueRef(std::move(k5)); // ok 👌
}

0개의 댓글