[C++] move 함수
1. move
template< class T >
typename std::remove_reference<T>::type&& move( T&& t ) noexcept; // (until C++14)
template< class T >
constexpr std::remove_reference_t<T>&& move( T&& t ) noexcept; // (since C++14)
- 객체 t가 이동 될 수 있음 을 알려줌
- t가 가진 자원을 다른 객체에게 효율적으로 전달
2. 코드로 알아보기
- web_compiler 에서 확인
2.1. 복사 생성자를 const lvalue reference로 만드는 이유
#include <iostream>
using namespace std;
class Point
{
int x = 0;
int y = 0;
public:
Point() : x(0), y(0) {}
Point(int a, int b) : x(a), y(b) {}
// 복사 생성자 모양
// 1. call by value, 복사 생성자가 무한히 호출되는 표기라서 컴파일 에러
// 2. call by reference, 임시객체를 받을 수 없음, 값타입을 반환하는 함수의 리턴값을 받을 수 없음
// 3. const lvalue reference
Point(const Point& pt) : x(pt.x), y(pt.y) {}
};
Point foo()
{
Point pt;
return pt;
}
int main()
{
Point p1;
Point p2(1, 2);
Point p3(p2); // Point( Point ) 모양의 생성자 필요
Point p4 = foo(); // 임시객체는 rvalue이기 때문에 참조를 받지 못함
}
2.2. move 생성자 개념
#include <iostream>
#include <string.h>
using namespace std;
class Cat
{
char* name;
int age;
public:
Cat(const char* s, int a) : age(a)
{
name = new char[strlen(s) + 1];
strcpy(name, s);
}
// 깊은 복사로 구현한 복사 생성자
Cat(const Cat& c) : age(c.age)
{
// 메모리 할당 후, 메모리를 통째로 복사
name = new char[strlen(c.name) + 1];
strcpy(name, c.name);
}
~Cat() { delete[] name; }
};
int main()
{
Cat c1("NABI", 2);
Cat c2 = c1;
}
#include <iostream>
#include <string.h>
using namespace std;
class Cat
{
char* name;
int age;
public:
Cat(const char* s, int a) : age(a)
{
name = new char[strlen(s) + 1];
strcpy(name, s);
}
~Cat() { delete[] name; }
Cat(const Cat& c) : age(c.age)
{
cout << "copy" << endl;
name = new char[strlen(c.name) + 1];
strcpy(name, c.name);
}
// 임시객체(rvalue)를 복사할 때 사용할 새로운 생성자
// "move" 생성자 라고 부름
Cat(Cat&& c) : age(c.age), name(c.name)
{
cout << "move" << endl;
c.name = nullptr; // 원본객체의 자원 reset
}
};
int main()
{
Cat c1("NABI", 2);
Cat c2 = c1; // copy
Cat c3 = static_cast<Cat&&>(c2); // lvalue지만 copy 후 삭제, move
Cat c4 = std::move(c1); // 위처럼 캐스팅, move
}
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
string s1 = "hello";
// string s2 = s1;
string s2 = std::move(s1);
cout << s1 << endl;
vector<int> v1(10, 3);
// vector<int> v2 = v1;
vector<int> v2 = std::move(v1);
cout << v1.size() << endl; // 0
}
2.3. move 개념 활용
- 클래스를 만들 때 되도록 move 생성자를 제공, 그렇지 않으면 복사 생성자를 사용하게 됨
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
// move와 알고리즘
// 아래 코드는 복사에 의한 swap, 성능 저하의 요인
/*
template<typename T> void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
*/
template<typename T> void Swap(T& a, T& b)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
int main()
{
string s1 = "hello";
string s2 = "world";
Swap(s1, s2);
}
2.4. rule of 5
- C++98 : rule of 3 (소멸자, 복사생성자, 대입연산자)
- C++11 : rule of 5 (+ move 생성자, move 대입연산자)
#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
class Data
{
int* buff;
string str;
int data;
public:
Data(string s) : str(s) { buff = new int[100]; }
~Data() { delete[] buff; }
// 복사 생성자
Data(const Data& d) : str(d.str), data(d.data)
{
// 포인터 멤버는 메모리 자체를 복사
buff = new int[100];
memcpy(buff, d.buff, sizeof(int) * 100);
}
// 대입 연산자
Data& operator=(const Data& d)
{
// 대입 연산자는 항상 자신과의 대입을 조사해야 함
if (&d == this) return *this;
str = d.str;
data = d.data;
// 기존 버퍼는 제거( 메모리 크기가 다를 수 있으므로 )
delete[] buff;
buff = new int[100];
memcpy(buff, d.buff, sizeof(int) * 100);
return *this;
}
// move 생성자
Data(Data&& d) : str(std::move(d.str)), data(d.data), buff(d.buff)
{
d.buff = nullptr;
}
// move 대입 연산자
Data& operator=(Data&& d)
{
// 대입 연산자는 항상 자신과의 대입을 조사해야 함
if (&d == this) return *this;
str = std::move(d.str);
data = d.data;
// 기존 버퍼는 제거( 메모리 크기가 다를 수 있으므로 )
delete[] buff;
buff = d.buff;
d.buff = nullptr;
return *this;
}
};
int main()
{
Data d1("AA");
//Data d2 = d1; // 복사 생성자
Data d3("BB");
d3 = d1; // 대입 연산자 d3.operator=(d1)
//d3 = d3;
}
2.5. move와 noexcept
- noexcept
- 함수가 예외가 없다고 컴파일러에게 알려주는 것
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
using namespace std;
class Object
{
string data;
public:
Object() = default;
~Object() {}
Object(const Object& o) : data(o.data) { cout << "Copy Ctor" << endl; }
Object& operator=(const Object& o)
{
cout << "Copy =" << endl;
if (&o == this) return *this;
data = o.data;
return *this;
}
// move 계열 함수를 만들 때
// 1. 예외가 없도록 만들고
// 2. 함수 () 뒤에 noexcept를 붙이는 것이 좋다.
// noexcept : 예외가 없다.
// noexcept(true) : 예외가 없다.
// noexcept(false) : 예외가 있다.
Object(Object&& o) noexcept( is_nothrow_move_constructible_v<string> )
: data(move(o.data)){ cout << "Move Ctor" << endl; }
Object& operator=(Object&& o) noexcept(
is_nothrow_move_constructible_v<string>)
{
cout << "Move =" << endl;
if (&o == this) return *this;
data = std::move(o.data);
return *this;
}
};
int main()
{
vector<Object> v(5);
v.resize(10); // move 생성자 5번, vector의 5개 object를 move로 옮김
Object o1;
Object o2 = o1; // 복사생성자
Object o3 = std::move(o1); // move 생성자
Object o4 = std::move_if_noexcept(o2); // 예외가 없으면 move, 예외가 있으면 복사 생성자 호출
}
2.6. move와 상수객체
- const 객체는 move가 될 수 없음, copy가 됨
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
using namespace std;
class Object
{
string data;
public:
Object() = default;
~Object() {}
Object(const Object& o) : data(o.data) { cout << "Copy Ctor" << endl; }
Object& operator=(const Object& o)
{
cout << "Copy =" << endl;
if (&o == this) return *this;
data = o.data;
return *this;
}
Object(Object&& o) noexcept : data(move(o.data)) { cout << "Move Ctor" << endl; }
Object& operator=(Object&& o ) noexcept
{
cout << "Move=" << endl;
if (&o == this) return *this;
data = move(o.data);
return *this;
}
};
// const 개체는 move 될 수 없음!!
int main()
{
const Object o1;
Object o2 = o1; // 복사
Object o3 = move(o1); // 복사
Object o4 = static_cast<const Object&&>(o1); // 복사
}
2.7. rule of 0
- copy / move 계열을 만들지 않으면 컴파일러가 모두 제공
- copy 계열을 사용자가 제공하면 컴파일러는 move 계열을 제공하지 않음
- rule of 0
- 자원을 직접 관리하지 말고 자원 관리 클래스를 사용
- copy / move 계열을 사용자가 만들 필요가 없음
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
using namespace std;
class Object
{
string data;
public:
Object() = default;
~Object() {}
Object(const Object& o) : data(o.data) { cout << "Copy Ctor" << endl; }
Object& operator=(const Object& o)
{
cout << "Copy =" << endl;
if (&o == this) return *this;
data = o.data;
return *this;
}
Object(Object&& o) noexcept : data(move(o.data)) { cout << "Move Ctor" << endl; }
Object& operator=(Object&& o)noexcept
{
cout << "Move =" << endl;
if (&o == this) return *this;
data = move(o.data);
return *this;
}
};
class Data
{
string s;
vector<int> v;
Object obj;
public:
Data() = default;
};
int main()
{
Data d1;
Data d2 = d1; // copy
Data d3 = move(d1); // move
}
참고
codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
코드누리
cppreference
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
댓글남기기