참고하면 좋을 사이트 1
참고하면 좋을 사이트 2
이미지 출처
복사/이동의 차이는
'원본 데이터를 유지해야 하는지'에 있다.
이동은 복사와는 조금 다른 것이, 원본은 정말 사용하지 않겠다는 의미이다.
경우에 따라 복사 비용을 엄청! 절감할 수 있다 ⭐️
왼값, 오른값
l value
: 단일식을 넘어서 계속 지속되는 개체
r value
: l value
가 아닌 나머지
오른값 참조
void TestKnight_Copy(Knight knight) { } // 복사
void TestKnight_LValueRef(Knight& knight) { } // 왼값 참조 (비const)
void TestKnight_ConstLValueRef(const Knight& knight) { } // 왼값 참조 const
void TestKnight_RValueRef(Knight&& knight) { } // 오른값 참조 // 소유권 자체를 넘긴다
int main()
{
Knight k1;
TestKnight_Copy(k1);
TestKnight_LValueRef(k1);
// TestKnight_LValueRef(Knight()); // error
TestKnight_ConstLValueRef(Knight());
TestKnight_RValueRef(Knight());
// 왼값 참조처럼 원본을 고칠 수 있다는, 굉장히 큰 권한을 얻게 된 것이다
// 근데 이제 원본이 더 이상 필요가 없어졌다. 소유권도 가질 수 있게 된다 ⭐️
TestKnight_RValueRef(static_cast<Knight&&>(k1));
return 0;
}
𝑸. C++ 컴파일러는 클래스에 대해 어떤 함수들을 암시적으로 생성할 수 있는가?
𝐀. 생성자, 소멸자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자 를 생성한다.
생성자
소멸자
복사 생성자
Knight(const Knight& knight)
{
}
void operator=(const Knight& knight)
{
_hp = knight._hp;
// 깊은 복사로, 얕은복사 문제 해결! 그러나 복사 비용이 엄청 클 수 있다.
if(knight._pet)
_pet = new Pet(*knight._pet);
}
Knight(Knight&& knight) noexcept
{
}
void operator=(Knight&& knight) noexcept
{
_hp = knight._hp;
_pet = knight._pet;
knight._pet = nullptr;
}
int main()
{
Knight k1;
Knight k2;
// 아래 두 코드는 완전히 같은 의미이다
k2 = static_cast<Knight&&>(k1);
k2 = std::move(k1);
}
/* 값 복사. 원본에 영향을 미치지 않는다. */
void TestKnight_Copy(Knight knight)
{
Knight._hp = 100;
}
/*
참조 방식. 왼값 참조라고 생각해도 좋다.
원본 주소값을 넘겨주는 방식으로,
내부적으로 코드를 살펴보면 포인터를 넘겨주는 것과 차이가 없다.
원본에 영향을 미칠 수 있다.
*/
void TestKnight_LValueRef(Knight& knight)
{
Knight._hp = 100;
}
/* const 로 받으므로 원본 변경이 불가능하다 */
void TestKnight_ConstLValueRef(const Knight& knight)
{
Knight._hp = 100;
}
/*
참조의 참조가 아니라, 오른값 참조라는 것
원본에 영향을 미칠 수 있는데
더 이상 활용하지 않을 예정이라서 마음대로 하라는 뜻
*/
void TestKnight_RValueRef(Knight&& knight)
{
}
int main()
{
Knight k1;
// 컴파일러 오류 : 비 const 참조에 대한 초기값은 lvalue여야 한다.
// 논리적으로도 앞뒤가 맞지 않다. 임시 객체를 수정한다니 (???)
TestKnight_LValueRef(Knight());
// 실행 가능
TestKnight_ConstLValueRef(Knight());
// 컴파일러 오류 : rvalue 참조를 lvalue 에 바인딩할 수 없다.
TestKnight_RValueRef(k1);
// 실행 가능
TestKnight_RValueRef(Knight());
TestKnight_RValueRef(static_cast<Knight&&>(k1));
}
class Pet
{
};
class Knight
{
public:
Knight()
{
cout << "Knight()" << endl;
}
// 복사 생성자
Knight(const Knight& knight)
{
cout << "const Knight()" << endl;
}
// 이동 생성자
Knight(Knight&& knight)
{
}
~Knight()
{
if(_pet)
delete _pet;
}
// 복사 대입 연산자
// 복사는 "복사 생성자" 또는 "복사 대입 연산자" 에서 이뤄진다
void operator=(const Knight& knight)
{
cout << "operator=(const Knight&)" << endl;
_hp = knight._hp;
// 깊은 복사
// 단점 : 코스트가 비싸질 수 있다, 원본을 참고하고 돌려준다 (원본 훼손 불가 ⭐️)
if(knight._pet)
_pet = new Pet(*Knight._pet);
}
// 이동 대입 연산자
// 장점: 원본 훼손 가능 ⭐️, 얕은 복사로도 쉽게 사용 가능
void operator=(Knight&& knight) noexcept
{
cout << "operator=(Knight&&)" << endl;
_hp = knight._hp;
//if(knight._pet)
// _pet = new Pet(*knight._pet);
_pet = knight._pet; // ⭐️
knight._pet = nullptr;
}
public:
int _hp = 100;
Pet* _pet;
};
int main()
{
Knight k2;
k2._pet = new Pet();
k2._hp = 1000;
Knight k3;
k3 = std::move(k2); // 오른값 참조로 캐스팅 // k3 = static_cast<Knight&&>(k2);
std::unique_ptr<Knight> uptr = std::make_unique<Knight>();
std::unique_ptr<Knight> uptr2 = std::move(uptr);
}