C++ Chapter 15.4 : std::move
카테고리: Cpp
태그: Cpp Programming
인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!
chapter 15. 의미론적 이동과 스마트 포인터
15.4 std::move
#include <utility>
해주어야 사용 가능하다.std::move(A)
- A 를 R-value로 변환하여 이동생 성자 혹은 이동 대입 연산자가 호출되게끔 한다.
🔔 std::move 쓰기 전
📜AutoPtr.h
- 복사 생성자, 이동 생성자 둘 다 가지고 있는 상황
#include <iostream>
using namespace std;
template<typename T>
class AutoPtr
{
public:
T* m_ptr;
public:
AutoPtr(T* ptr = nullptr)
:m_ptr(ptr)
{
cout << "AutoPtr default constructor" << endl;
}
~AutoPtr()
{
cout << "AutoPtr destructor" << endl;
if (m_ptr != nullptr) delete m_ptr;
}
AutoPtr(const AutoPtr& a) // 💎복사 생성자💎
{
cout << "AutoPtr copy constructor" << endl;
// deep copy
m_ptr = new T;
*m_ptr = *a.m_ptr; // 📜Resource.h 의 대입 연산자 호출하여 깊은 복사 수행
}
AutoPtr& operator = (const AutoPtr& a) // 💎대입 연산자 오버로딩💎
{
cout << "AutoPtr copy assignment" << endl;
if (&a == this)
return *this;
if (m_ptr != nullptr) delete m_ptr;
// deep copy
m_ptr = new T;
*m_ptr = *a.m_ptr; // 📜Resource.h 의 대입 연산자 호출하여 깊은 복사 수행
return *this;
}
//AutoPtr(const AutoPtr& a) = delete;
//AutoPtr& operator = (**const** AutoPtr& a) = delete;
AutoPtr(AutoPtr&& a) // 💎이동생성자💎
: m_ptr(a.m_ptr) // 소유권 이전
{
a.m_ptr = nullptr; // 소유권 박탈
cout << "AutoPtr move constructor" << endl;
}
AutoPtr& operator = (AutoPtr&& a) // 💎이동 대입 연산자 오버로딩💎
{
cout << "AutoPtr move assignment" << endl;
if (&a == this)
return *this;
if (m_ptr != nullptr) delete m_ptr;
m_ptr = a.m_ptr; // 소유권 이전
a.m_ptr = nullptr; // 소유권 박탈
return *this;
}
T& operator *() const { return *m_ptr; }
T* operator ->() const { return m_ptr; }
bool inNull() const { return m_ptr == nullptr; }
};
📜main.cpp
#include <iostream>
int main()
{
{
AutoPtr<Resource> res1(new Resource(10000000));
cout << res1.m_ptr << endl;
AutoPtr<Resource> res2 = res1; // 복사 생성자가 호출된다. res1은 L-Value니까
cout << res1.m_ptr << endl;
cout << res2.m_ptr << endl;
}
}
💎출력💎
Resource length constructed
AutoPtr default constructor
0033F510
AutoPtr copy constructor
Resource default constructed
Resource copy assignment
0033F510
0033F5F0
AutoPtr destructor
Resource destroyed
AutoPtr destructor
Resource destroyed
AutoPtr<Resource> res2 = res1;
👉 이동 생성자, 복사 생성자. 둘다 구현되있는 상태에서 어떤게 호출될까?
- 일단 객체 생성 과정이므로 복사 생성자 or 이동 생성자 호출
- 복사 생성자가 호출된다.
- 복사 대상이 되는
res1
은 L-Value 니까 R-value만 받는 이동 생성자는 이를 받을 수 없으므로. - 깊은 복사가 이루어 진다.
- 깊은 복사를 수행하는 Resource의 대입 연산자 호출.
res2
는 자기만의 새로운 별개의 공간을 할당 받아 그곳에res1
의 내용물들을 복사하였다.res1
와res2
는 별개. 주소가 다르다.- 0033F510, 0033F5F0 둘이 주소 다름
- 복사 대상이 되는
🔔 std::move 로 이동 생성자 호출하기
L-value이지만 이동 생성자를 호출하고 싶다면? 👉
std::move
를 통해 R-value로 바꿔주면 된다.
📜main.cpp
#include <iostream>
#include <utility> // ✨✨
int main()
{
{
AutoPtr<Resource> res1(new Resource(10000000));
cout << res1.m_ptr << endl;
AutoPtr<Resource> res2 = std::move(res1); // 이동 생성자가 호출된다. std::move는 res1을 R-Value 로 바꿔준다.
cout << res1.m_ptr << endl;
cout << res2.m_ptr << endl;
}
}
💎출력💎
Resource length constructed
AutoPtr default constructor
001FF540
AutoPtr move constructor
00000000
001FF540
AutoPtr destructor
Resource destroyed
AutoPtr destructor
- AutoPtr<Resource> res2 =
std::move(res1);
- res1을 R-Value로 리턴해준다.
std::move
덕에 이동생성자가 호출된다. R-Value 가 전달 되었기 때문에.- 깊은 복사 과정 없음. 따라서 Resource 디폴트 생성자 호출과 Resource 대입 연산자는 호출 되지 않는다.
- 소유권 이전과 박탈로
res1
포인터는 nullptr로 초기화 하고 res1 포인터가 한때 맡았던 객체를 이젠res2
가 맡게 된다.- res2 주소값이 기존의 res1 주소값이 된 것을 볼 수 있음
001FF540
- res2 주소값이 기존의 res1 주소값이 된 것을 볼 수 있음
- 📢 주의할점 !!!
- 이후에
res1
은 nullptr로 초기화 되기 때문에 이걸로 뭘 하려고 하는건 위험함- 잘못 사용하면 독이 될 수도.
std::move
는 프로그래머가 직접 사용하는 것이기 때문에 이 실수는 프로그래머 책임임
- 이후에
- res1을 R-Value로 리턴해준다.
🔔 Copy Semantics VS Move Semantics
#include <iostream>
#include <utility> // ✨✨
#include <vector>
int main()
{
{
vector<string> v;
string str = "Hello";
v.push_back(str);
cout << str << endl;
cout << v[0] << endl;
v.push_back(std::move(str));
cout << str << endl;
cout << v[0] << " " << v[1] << endl;
}
}
💎출력💎
Hello
Hello
👈 null 이어서 비워진 부분
Hello Hello
std::vector의 push_back은 L-Value Reference 와 R-Value Reference 에 따라 오버로딩이 되어있음
- 💙copy semantics 사용
string str = "Hello";
str
은 L-Value다. v.push_back(str); 에서도 L-Value로 받아들이고 있다.- 따라서 “Hello” 를 변수로서 받아들이고 있다는 것이다. 이말은 즉 "Hello"는 str이라는 이름의 다른 메모리 공간에 존재 중.
- v[0] 이라는 새로운 공간을 만들어 str의 내용물을 깊은 복사 해온 것이다.
- v[0] 과
str
은 같은 내용물을 가지고 있지만 별개의 공간이다.- 단순히 깊은 복사로 내용물을 복사해온 것이라서 여전히 str이 가리키는 곳에도 “Hello”객체가 있고 v[0]이 가리키는 곳에도 “Hello” 객체가 있다.
- v[0] 과
- 💙move semantics 사용
- v.push_back(std::move(str));
- R-Value 로서 str를 v[1]에 move 시킴
- 이말은 즉 '복사 없이' str이 맡았던 "Hello" 객체는 이제 v[1]이 맡게 되고 str은 nullptr로 초기화가 된다는 얘기.
- str공간에 있던 “Hello” 객체가 복사된 것이 아닌 v[1]로 ‘이동’이 된 것.
- v[1]은 기존 Hello 객체를 그대로 소유권을 이동받고 str 은 비워지게 된다.
- v.push_back(std::move(str));
🔔번외) 이동 생성자가 늘 더 빠른 것은 아니다.
move 연산으로 더 성능이 좋아질 수 있는 케이스를 파악하는 것이 중요함
- 항상 move 연산이 복사보다 더 빠른 것은 아니다.
std::string
에서는 move 연산을 안쓰는 것이 더 낫다.std::string
는 move를 지원하긴 하는데 문자열 길이가 짧은 경우엔 move연산보단 복사가 더 성능이 낫다고 한다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기