53
rvalue reference 강강강

Rvalue reference 강기완. string s0("my mother told me that"); string s1("cute"); string s2("fluffy"); string s3("kittens"); string s4("are an essential part

Embed Size (px)

Citation preview

rvalue reference

강기완

string s0("my mother told me that");string s1("cute");string s2("fluffy");string s3("kittens");string s4("are an essential part of a healthy diet");

string dest = s0 + " " + s1 + " " + s2 + " " + s3 + " " + s4;

기존 operator+() x 8• 임시변수 생성 , reserve()• append(left), append(right)

rvalue reference operator+()• left.append(right)• std::move(left) (stealing)

stealing

A

“merong”

Bstd::string

std::string

char*NULL

rvalue reference 를 알기 전에 rvalue 부터 알아야 ..

rvalue ?

오른쪽에 있는 값 in C

rvalue in C++ ?표현식의 끝 (;) 에서 사라지는 임시값‘r’ 은 이제 아무런 의미가 없다 !

lvalue in C++ ?

표현식이 넘어가도 사라지지 않는 값

lvalue 의 예

obj, *ptr, ptr[index], ++x

rvalue 의 예

1729, x + y, std::string(“meow”), x++

lvalue 와 rvalue 를 구분하는 방법

“ 그 값의 주소를 얻을 수 있는가 ?”

주소를 얻을 수 있다면 lvalue

&obj, &*ptr, &ptr[index], &++x

&1729, &(x + y), &std::string(“meow”), &x++

함수 호출

string& lvalue() { ... }string rvalue() { ... }

함수가 값을 리턴하면 “호출”이 rvalue,

reference 를 리턴하면 lvalue

T& operator ++(T& a);

T operator ++(T& a, int);

++X

X++

RULE: const correctness

const 타입의 값은 변경할 수 없다 .

string one("cute");const string two("fluffy");string three() { ... }const string four() { ... }  one;     // modifiable lvaluetwo;     // const lvaluethree(); // modifiable rvaluefour();  // const rvalue

void foo(string& str) {}void bar(const string& str) {}

foo(one);//foo(two); // 에러발생foo(three()); // 원래 C++ 은 이것을 허용하지 않지만 VC 는 이것을 허용한다 . Warning level4 에서 warning 발생//foo(four()); // 에러발생bar(one);bar(two);bar(three());bar(four());

remind: rvalue 는 ?임시값 ! 표현식 끝에서 사라지는 것을 보장한다 .“ 이후에 아무도 rvalue 를 사용할 수는 없어 !” 내 맘대로 바꿔도 OK!

이제부터 rvalue reference

Type&&, const Type&&

string&&, const string&&

lvalue 와 rvalue 는 거의 비슷하지만

초기화 , overloading resolution 이 다르다 .

string modifiable_lvalue("kittens");const string const_lvalue("hungry hungry zombies");string modifiable_rvalue() { ... }const string const_rvalue() { ... } string& a = modifiable_lvalue;string& b = const_lvalue;               // ERRORstring& c = modifiable_rvalue();        // warning (VC)string& d = const_rvalue();             // ERROR

const string& e = modifiable_lvalue;const string& f = const_lvalue;const string& g = modifiable_rvalue();const string& h = const_rvalue();

warning C4239: nonstandard extension used : 'initial-izing' : conversion from 'std::string' to 'std::string &'

string modifiable_lvalue("kittens");const string const_lvalue("hungry hungry zombies");string modifiable_rvalue() { ... }const string const_rvalue() { ... } string&& i = static_cast<string&&>(modifiable_lvalue);string&& j = const_lvalue;              // ERRORstring&& k = modifiable_rvalue();string&& l = const_rvalue();            // ERROR const string&& m = static_cast<const string&&>(modifiable_lvalue);const string&& n = static_cast<const string&&>(const_lvalue);const string&& o = modifiable_rvalue();const string&& p = const_rvalue();

void meow(string& s) {    cout << "meow(string&): " << s << endl;} void meow(const string& s) {    cout << "meow(const string&): " << s << endl;} void meow(string&& s) {    cout << "meow(string&&): " << s << endl;} void meow(const string&& s) {    cout << "meow(const string&&): " << s << endl;} string rvalue() { return “rvalue()”; } const string const_rvalue() { return “const_rvalue()”; }

int main() {    string lvalue(“lvalue");    const string const_lvalue(“const_lvalue");     meow(lvalue);    meow(const_lvalue);    meow(rvalue());    meow(const_rvalue());} C:\Temp>cl /EHsc /nologo /W4 four_overloads.cppfour_overloads.cpp C:\Temp>four_overloadsmeow(string&): lvaluemeow(const string&): const_lvaluemeow(string&&): rvalue()meow(const string&&): const_rvalue()

void purr(const string& s) {    cout << "purr(const string&): " << s << endl;} void purr(string&& s) {    cout << "purr(string&&): " << s << endl;} string rvalue() { return “rvalue()”; } const string const_rvalue() { return “const_rvalue()”; }

int main() {    string lvalue(“lvalue");    const string const_lvalue(“const_lvalue");     purr(lvalue);    purr(const_lvalue);    purr(rvalue());    purr(const_rvalue());} C:\Temp>cl /EHsc /nologo /W4 two_overloads.cpptwo_overloads.cpp C:\Temp>two_overloadspurr(const string&): lvaluepurr(const string&): const_lvaluepurr(string&&): rvalue()purr(const string&): const_rvalue()

const string&

string&&

lvalue

const lvalue

rvalue

const rvalue

overload resolution 기본 규칙

const correctnesslvalue 는 lvalue reference 로rvalue 는 rvalue reference 로 바인딩 하려는 성향이 있다 .( 강함 )

modifiable 은 modifiable reference 로 바인딩 하려는 성향이 있다 . ( 약함 )

const string&

string&&

lvalue

const lvalue

rvalue

const rvalue

const string&

string&&

lvalue

const lvalue

rvalue

const rvalue

move semantics

class remote_integer {public:    remote_integer() {        cout << "Default constructor." << endl;        m_p = NULL;    }     explicit remote_integer(const int n) {        cout << "Unary constructor." << endl;        m_p = new int(n);    }     remote_integer(const remote_integer& other) {        cout << "Copy constructor." << endl;        if (other.m_p) {            m_p = new int(*other.m_p);        } else {            m_p = NULL;        }    }

    remote_integer(remote_integer&& other) {        cout << "MOVE CONSTRUCTOR." << endl;         m_p = other.m_p;        other.m_p = NULL;    }

int get() const {        return m_p ? *m_p : 0;    } private:    int * m_p;

move 생성자와 move 대입 연산자는 자동으로 생성되지 않는다 !

    remote_integer& operator=(const remote_integer& other) {        cout << "Copy assignment operator." << endl;         if (this != &other) {            delete m_p;             if (other.m_p) {                m_p = new int(*other.m_p);            } else {                m_p = NULL;            }        }         return *this;    }     remote_integer& operator=(remote_integer&& other) {        cout << "MOVE ASSIGNMENT OPERATOR." << endl;         if (this != &other) {            delete m_p;             m_p = other.m_p;            other.m_p = NULL;        }         return *this;    }

const remote_integer&& 를 사용할 수 없음을 알아두자 !

int main() {    remote_integer a(8);    remote_integer b(10);      b = square(a);}

MOVE

대입

연산

자 remote_integer square(const remote_integer& r) {    const int i = r.get();    return remote_integer(i * i);}

잠깐 ! square() 가 const remote_integer 를 리턴한다면 ?move 대입 연산자를 사용하지 않는다 .

암시적 기본 생성자사용자 선언 생성자 :• 복사 생성자• Move 생성자

억제

class B {public: //B(const B& b) {} B(B&& b) {}};

B b; 기본 생성자가 억제되므로 컴파일 에러 !

Move 생성자는 복사 생성자를 억제하지 못한다 .Move 대입 연산자는 기본 대입 연산자를 억제하지 못한다 .

이름 있는 rvalue reference 는 lvalue 이다 .

string&& r = rvalue();

    remote_integer(remote_integer&& other) {        cout << "MOVE CONSTRUCTOR." << endl;        m_p = other.m_p;        other.m_p = NULL;    }

    remote_integer(remote_integer&& other) {        cout << "MOVE CONSTRUCTOR." << endl;        m_p = NULL;        *this = other; // WRONG    }

remote_integer(remote_integer&& other) {        cout << "MOVE CONSTRUCTOR." << endl;        m_p = NULL;        *this = std::move(other); // RIGHT    }

template <typename T> struct RemoveReference {     typedef T type;}; template <typename T> struct RemoveReference<T&> {     typedef T type;}; template <typename T> struct RemoveReference<T&&> {     typedef T type;}; template <typename T> typename RemoveReference<T>::type&& Move(T&& t) {    return t;}

lvalue 값이 더 이상 필요하지 않을 때 , move semantics 를 활성화 하기 위해 std::move(lvalue 표현식 ) 을 사용할 수 있다 .std::move() 는 상수성 (constness) 을 유지하면서 lvalue 를 rvalue 로 바꿔준다 .

perfect forwarding

template <typename T> void outer(T& t) {    inner(t);}

임의의 단항 함수 inner() 에 인자를 전달할 경우

template <typename T> void outer(T& t) {    inner(t);}

outer(5); // ERROR

outer<const int>(5); // OK

const int i = 5;outer(i) // OK: T = const int

그런데 , inner(const int&) 이라면 ?

template <typename T> void outer(T& t) {    inner(t);}

template <typename T> void outer(const T& t) {    inner(t);}

단항 함수에 인자를 전달하려면 두 개의 함수 템플릿이 필요하다 .

template <typename T> void outer(...) {    inner(...);}

매개변수가 여러 개라면 그 수의 지수만큼의 오버로드 함수가 있어야 한다 .

VC9 SP1 의 tr1::bind() 는 5 개의 인자에 대해 63 개의 오버로드를 만든다 .

perfect forwarding:

이제 rvalue 도 추론할 수 있다 .그리고 constness/lvaluenss/rvalueness 를 보존한다 .

template <typename T> struct Identity {    typedef T type;}; template <typename T> T&& Forward(typename Identity<T>::type&& t) {    return t;} void inner(int&, const int&) {    cout << "inner(int&, const int&)" << endl;} void inner(const int&, int&) {    cout << "inner(const int&, int&)" << endl;} template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) {    inner(Forward<T1>(t1), Forward<T2>(t2));}

이름 있는 rvalue reference 는 lvalue 이므로std::forward() 를 사용해야 한다 .std::forward() 는 타입을 보존하면서 이름을 없애준다 .

Identity 는 Forward() 에서 템플릿 인자 추론을못하게 막는다 . Identity 가 없다면 Forward() 의 T 가Type& 로 추론되어 Type& 을 리턴할 수 있다 .

어떻게 이런게 가능할까 ?

rvalue reference 템플릿 인자 추론

C++11 에서 추론 방식이 좀 바뀌었다 .아마 ~ rvalue reference 때문 ?

스페샬 ~ 룰 !

C++11 에서 추론 방식이 좀 바뀌었다 .아마 ~ rvalue reference 때문 ?

template <typename T> void quark(T&& t);

string a; // lvalueconst string b; // lvaluequark(a);quark(b);

andT (const) string&

요런 경우엔 ‘스페샬 룰’이 적용 되지 않는다 .

template <typename T> void quark(T& t);template <typename T> void quark(const T& t);

template <typename T> void quark(const T&& t);

참조자 축소

T 가 string& 면 T&& 는 ‘ string& &&’ 인데 ,이건 어떻게 ?

template <typename T> void quark(T&& t);

string a; // lvaluequark(a);

X& &&, X&& &, X& & X&X&& && X&&

template <typename T> void quark(T&& t) { Name<T&&>::get();}

T string& 라면 T&& 는 ‘ string& &&’ 이 되고 ,참조자가 축소되어 ‘ string&’ 으로 바뀐다 .결국 , Name<string&>::get() 이 수행된다 .

T string&Name<string&>::g

et()

template <typename T>T&& forward(typename Identity<T>::type&& t);

template <typename T> void outer(T&& t) { inner(forward<T>(t));}

outer arg(string)

T T&&

lvalue string& string&

const lvalue const string& const string&

rvalue string string&&

const rvalue const string const string&&

끗 !