Rvalue Reference and constexpr 김경진 Microsoft MVP(Visual C++)
vector<vector<int>> user-defined literals thread_local vector<localtype> initializer lists template aliases constexpr lambdas []{ foo(); } unique_ptr<t> shared_ptr<t> weak_ptr<t> nullptr =default, =delete regex C++ raw string literals override, final auto i = v.begin(); thread, mutex for (x : coll) async atomic<t> variadic templates template <typename T > function<> strongly-typed enums enum class E { }; auto f() -> int array<t, N> noexcept decltype extern template unordered_map<int, string> delegating constructors rvalue references (move semantics) future<t> static_assert(x) tuple<int, float, string>
vector<vector<int>> user-defined literals thread_local vector<localtype> initializer lists constexpr template aliases lambdas []{ foo(); } unique_ptr<t> shared_ptr<t> weak_ptr<t> nullptr =default, =delete regex C++ raw string literals override, final auto i = v.begin(); thread, mutex for (x : coll) async atomic<t> variadic templates template <typename T > function<> strongly-typed enums enum class E { }; auto f() -> int array<t, N> noexcept decltype extern template unordered_map<int, string> delegating constructors rvalue references (move semantics) future<t> static_assert(x) tuple<int, float, string>
Agenda Rvalue Reference Move Semantics Perfect Forwarding constexpr
C 언어의 Lvalue & Rvalue Lvalue 대입연산자 (=) 를기준으로왼쪽과오른쪽에모두사용될수있는값 Lvalue = Left Value Rvalue 대입연산자 (=) 를기준으로오른쪽에만사용될수있는값 Rvalue = Right Value
C++ 의 Lvalue & Rvalue L 과 R 은더이상 Left, Right 를의미하지않음 Left, Right 개념은잊어버리고, Lvalue 와 Rvalue 를단순한 고유명사로기억하자 Lvalue Rvalue 표현식이종료된이후에도없어지지않고지속되는개체 ( 예 : 모든변수 ) 표현식이종료되면더이상존재하지않은임시적인개체 ( 예 : 상수, 임시객체 )
C++ 의 Lvalue & Rvalue 오른쪽코드에서 Rvalue 를찾아보자 #include <iostream> #include <string> using namespace std; int main() { int x = 3; const int y = x; int z = x + y; int* p = &x; cout << string("hello"); } ++x; x++;
C++ 의 Lvalue & Rvalue 오른쪽코드에서 Rvalue 를찾아보자 #include <iostream> #include <string> using namespace std; int main() { int x = 3; const int y = x; int z = x + y; int* p = &x; cout << string("hello"); } ++x; x++;
C++ 의 Lvalue & Rvalue Q. 그래도 Lvalue 인지 Rvalue 인지헷갈린다면?
C++ 의 Lvalue & Rvalue Q. 그래도 Lvalue 인지 Rvalue 인지헷갈린다면? A. 주소연산자 & 를붙여서에러가나면 Rvalue &(++x); &(x++); // error C2102: '&' requires l-value
Rvalue Reference (&& 참조자 ) 지금까지사용했던참조자 (&) 는 Lvalue 참조자 C++11 표준에 Rvalue를참조하기위한 Rvalue Reference가추가됨 Rvalue 참조자문법 type-id && cast-expression ex) int&& n = rvalue();
Rvalue Reference (&& 참조자 ) Lvalue Reference : Lvalue만참조가능 Rvalue Reference : Rvalue만참조가능 int rvalue() { return 10; } int main() { int lvalue = 10; } int& a = lvalue; int& b = rvalue(); int&& c = lvalue; int&& d = rvalue(); // error C2440 // error C2440
Rvalue Reference (&& 참조자 ) Q. Rvalue Reference 는 Lvalue 일까? Rvalue 일까?
Rvalue Reference (&& 참조자 ) Q. Rvalue Reference 는 Lvalue 일까? Rvalue 일까? A. Lvalue (Rvalue Reference Rvalue) 이후에나올내용을이해하는데중요한개념
Move Semantics 도입배경 코드곳곳에서발생하는불필요한 Rvalue 복사과정, 이로인한오버헤드 std::string a, b = "Hello ", c = "world"; a = b + c; std::string appendstring(std::string param); std::string result = appendstring("hello");
Move Semantics 도입배경 코드곳곳에서발생하는불필요한 Rvalue 복사과정, 이로인한오버헤드 std::string a, b = "Hello ", c = "world"; a = b + c; std::string 임시객체 std::string appendstring(std::string param); std::string result = appendstring("hello"); std::string 임시객체 std::string 임시객체
Move Semantics 도입배경 std::string a, b = "Hello ", c = "world"; a = b + c; std::string b + c (Rvalue) size = 12 H e l l o w o r l d Copy std::string a size = 12 H e l l o w o r l d
Move Semantics 도입배경 std::string a, b = "Hello ", c = "world"; a = b + c; std::string b + c (Rvalue) size = 0 H e l l o w o r l d std::string a size = 12 H e l l o w o r l d
Move Semantics 도입배경 std::string a, b = "Hello ", c = "world"; a = b + c; std::string b + c (Rvalue) size = 0 Move std::string a size = 12 H e l l o w o r l d
Move Semantics 도입배경 임시객체 (Rvalue) 의복사 이동이되는것이상식적인개념 하지만 C++11 이전에는이러한개념을언어수준에서구현할방법이없었음 C++11 에이르러객체의이동이라는개념이도입됨 Move Semantics
Move Semantics 구현 Q. 내코드에서 Move Semantics 를지원하려면?
Move Semantics 구현 Q. 내코드에서 Move Semantics 를지원하려면? A-1. Move 생성자, Move 대입연산자구현 MemoryBlock(MemoryBlock&& other); MemoryBlock& operator=(memoryblock&& other);
Move Semantics 구현 Demo
Move Semantics 구현 Q. 내코드에서 Move Semantics 를지원하려면? A-2. Rvalue Reference 를파라미터로받는함수작성 std::string operator+(std::string&& left, std::string&& right) {... } std::string s = std::string("h") + "e" + "ll" + "o";
변화된코딩패러다임 컨테이너에객체를삽입할때더이상포인터를넣지않아도됨 std::vector<memoryblock*> v1; std::vector<memoryblock> v2; vector 컨테이너와같은대규모리소스를반환하는함수작성가능 void createvector(std::vector<memoryblock>& v) {... } std::vector<memoryblock> createvector() {... }
std::move 함수 Q. std::move 함수가수행하는일은?
std::move 함수 Q. std::move 함수가수행하는일은? A. 파라미터를 무조건 Rvalue Reference 로타입캐스팅 template<class T> typename remove_reference<t>::type&& move(t&& _Arg) { return static_cast<remove_reference<t>::type&&>(_arg); } 절대이름에낚이지말자!! std::move 호출만으로는아무것도이동시키지않음
std::move 함수 Lvalue 를 Rvalue 로취급하고싶을때사용 ( 컴파일러에게해당객체가이동해도무관한객체임을알려주기위해 ) class Person { public: void setname(std::string&& newname) { name = newname; } Person(Person&& other) { name = other.name; } std::string name; };
std::move 함수 Lvalue 를 Rvalue 로취급하고싶을때사용 ( 컴파일러에게해당객체가이동해도무관한객체임을알려주기위해 ) class Person { public: void setname(std::string&& newname) { name = newname; Rvalue 복사발생 } Person(Person&& other) { name = other.name; Rvalue 복사발생 } std::string name; };
std::move 함수 Lvalue 를 Rvalue 로취급하고싶을때사용 ( 컴파일러에게해당객체가이동해도무관한객체임을알려주기위해 ) class Person { public: void setname(std::string&& newname) { name = std::move(newname); } Person(Person&& other) { name = std::move(other.name); } std::string name; };
&& 참조자의두얼굴 오른쪽코드에서 Rvalue Reference 를찾아보자 1 2 3 4 void foo(string&& param); string&& var1 = string(); auto&& var2 = var1; template<typename T> void foo(t&& param);
&& 참조자의두얼굴 1 void foo(string&& param); 오른쪽코드에서 Rvalue Reference 를찾아보자 2 3 Rvalue Reference (O) string&& var1 = string(); auto&& var2 = var1; Rvalue Reference (X) template<typename T> void foo(t&& param); 4 Rvalue Reference (O) Rvalue Reference (X)
&& 참조자의두얼굴 && 참조자가템플릿함수의템플릿파라미터또는 auto 타입과함께사용되었을경우 Universal Reference template<typename T> void foo(t&& param); Universal Reference auto&& var2 = var1; Universal Reference
Universal Reference Lvalue 와 Rvalue 를모두참조할수있는 포괄적 (Universal) 레퍼런스 반드시 Rvalue Reference 와구분되어야함 Perfect Forwarding 구현을위한열쇠
Universal Reference Lvalue 와 Rvalue 를모두참조할수있는 포괄적 (Universal) 레퍼런스 반드시 Rvalue Reference 와구분되어야함 Perfect Forwarding 구현을위한열쇠
Old C++ 에서는해결할수없는문제가하나있었다.
Forwarding Problem Old C++ 에서는해결할수없는문제가하나있었다.
Forwarding Problem make_shared 함수와같은 factory 함수작성 template<typename T, typename Arg> T* factory(arg& arg) { return new T(arg); } struct X { X(int& n) {} }; int n = 0; X* px = factory<x>(n);
Forwarding Problem make_shared 함수와같은 factory 함수작성 template<typename T, typename Arg> T* factory(arg& arg) { return new T(arg); } struct Y { Y(const int& n) {} }; // error C2664: cannot convert argument 1 from 'int' to 'int & Y* py = factory<y>(10);
Forwarding Problem 결국 non-const 버전과 const 버전을모두제공해야함 template<typename T, typename Arg> T* factory(arg& arg) { return new T(arg); } template<typename T, typename Arg> T* factory(const Arg& arg) { return new T(arg); }
Forwarding Problem 작성해야하는함수의개수는 2 n 으로증가 (n = 파라미터개수 ) 가변인자함수는답이없음
Forwarding Problem 작성해야하는함수의개수는 2 n 으로증가 (n = 파라미터개수 ) 가변인자함수는답이없음 하지만 Universal Reference 가출동하면어떨까?
Perfect Forwarding 1 단계 Universal Reference 를이용한구현 template<typename T, typename Arg> T* factory(arg&& arg) { return new T(arg); } int n = 0; X* px = factory<x>(n); // OK Y* py = factory<y>(10); // OK
템플릿타입추론규칙 template <typename T> void deduce(t&& param); int n = 0; deduce(n); // Lvalue 전달 : T -> int& 로추론 deduce(10); // Rvalue 전달 : T -> int 로추론
템플릿타입추론규칙 template <typename T> void deduce(t&& param); int n = 0; deduce(n); // Lvalue 전달 : T -> int& 로추론 deduce(10); // Rvalue 전달 : T -> int 로추론 void deduce(int& && param); Void deduce(int&& param); // Lvalue 전달 // Rvalue 전달
Reference Collapsing Rules Reference 경합 T& & T& && T&& & T&& && Reference 붕괴 T& T& T& T&& void deduce(int& param); Void deduce(int&& param); // Lvalue 전달 // Rvalue 전달
Perfect Forwarding 2 단계 마지막으로해결해야할문제 Lvalue Lvalue, Rvalue Rvalue 로전달해야완벽한포워딩 template<typename T, typename Arg> T* factory(arg&& arg) { return new T(arg); }
Perfect Forwarding 2 단계 마지막으로해결해야할문제 Lvalue Lvalue, Rvalue Rvalue 로전달해야완벽한포워딩 template<typename T, typename Arg> T* factory(arg&& arg) { return new T(std::forward<Arg>(arg)); }
std::forward 함수 파라미터를 조건에따라 Rvalue Reference 로타입캐스팅 Rvalue/Rvalue Reference 일경우에만 Rvalue Reference 로타입캐스팅 Reference Collapsing Rules 를이용 template<class T> T&& forward(remove_reference<t>::type&& _Arg) { return (static_cast<t&&>(_arg)); } 이것도이름에낚이지말자!! std::forward 호출만으로는아무것도전달하지않음
std::move 와 std::forward Q. 둘중어떤함수를사용해야할지헷갈린다면?
std::move 와 std::forward Q. 둘중어떤함수를사용해야할지헷갈린다면? A. Rvalue Reference 에는 std::move 함수, Universal Reference 에는 std::forward 함수사용
constexpr 핵심엿보기 컴파일타임처리 메타프로그래밍
컴파일타임과런타임 컴파일타임 컴파일러가바이너리코드를만들어내는시점 런타임 프로그램을실행하여실제로동작되는시점
컴파일타임처리 런타임에수행할작업을컴파일타임에미리수행하여상수화 컴파일시간은다소늘어나지만런타임퍼포먼스는증가
메타프로그래밍 프로그램에대한프로그래밍 예 ) Excel 의매크로함수, lex & yacc 구문분석기 C++ 메타프로그래밍 컴파일타임에처리되는일련의작업을프로그래밍하는것
메타프로그래밍 C++ 메타프로그래밍의목적 프로그램이실행되기전에최대한많은일을해둠으로써퍼포먼스증가 템플릿메타프로그래밍 난이도가높고, 가독성이떨어짐 constexpr 을이용하면아주쉽게메타프로그래밍가능
constexpr specifier const, static 과같은한정자 ( 변수, 함수에사용 ) 컴파일타임에값을도출하겠다 라는의미를가짐
constexpr 변수 변수의값을컴파일타임에결정하여상수화하겠다 라는의미 반드시상수식으로초기화되어야함 constexpr int n = 0; constexpr int m = std::time(null); // OK // error C2127
constexpr 함수 함수파라미터에상수식이전달될경우, 함수내용을 컴파일타임에처리하겠다 라는의미 constexpr int square(int x) { return x * x; } 전달되는파라미터에따라컴파일타임, 런타임처리가결정됨 int n; std::cin >> n; square(n); // 런타임처리 square(10); // 컴파일타임처리
constexpr 함수제한조건 함수내에서는하나의표현식만사용할수있으며, 반드시리터럴타입을반환해야함 constexpr LiteralType func() { return expression; }
constexpr 함수로변환 if / else 구문 for / while 루프 변수선언
constexpr 함수로변환 if / else 구문 삼항연산자 (x > y? x : y) for / while 루프 재귀호출 변수선언 파라미터전달
2~n 소수의합구하기 bool IsPrime(int number) { if (number <= 1) return false; for (int i = 2; i * i <= number; ++i) if (number % i == 0) return false; } return true; int PrimeSum(int number) { int sum = 0; for (int i = number; i >= 2; --i) if (IsPrime(i)) sum += i; } return sum;
IsPrime 템플릿메타프로그래밍버전 struct false_type { typedef false_type type; enum { value = 0 }; }; struct true_type { typedef true_type type; enum { value = 1 }; }; template<bool condition, class T, class U> struct if_ { typedef U type; }; template <class T, class U> struct if_ < true, T, U > { typedef T type; }; template<size_t N, size_t c> struct is_prime_impl { typedef typename if_<(c*c > N), true_type, typename if_ < (N % c == 0), false_type, is_prime_impl<n, c + 1> > ::type > ::type type; enum { value = type::value }; }; template<size_t N> struct is_prime { enum { value = is_prime_impl<n, 2>::type::value }; }; template <> struct is_prime <0> { enum { value = 0 }; }; template <> struct is_prime <1> { enum { value = 0 }; };
IsPrime 템플릿메타프로그래밍버전
constexpr 을이용한메타프로그래밍 Demo
constexpr 관련라이브러리 Sprout C++ Libraries (Bolero MURAKAMI) http://bolero-murakami.github.io/sprout/
C++14 constexpr 제한조건완화 변수선언가능 (static, thread_local 제외 ) if / switch 분기문사용가능 range-based for 루프를포함한모든반복문사용가능
Summary 키워드 Rvalue 내용 표현식이종료되면사라지는임시적인개체 Rvalue Reference && 참조자를사용하여 Rvalue 참조가능 Move Semantics 객체의이동, Move 생성자, Move 대입연산자구현 Perfect Forwarding Universal Reference, std::forward 함수이용 constexpr 컴파일타임처리, 메타프로그래밍
참고자료 Effective Modern C++ (Scott Meyers) http://blogs.embarcadero.com/jtembarcadero/2012/11/12/my-top-5-c11-language-and-library-features-countdown/ http://msdn.microsoft.com/en-us/library/dd293668.aspx http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx http://msdn.microsoft.com/en-us/library/dd293665.aspx http://www.codeproject.com/articles/397492/move-semantics-and-perfect-forwarding-in-cplusplus http://www.codeproject.com/articles/453022/the-new-cplusplus-rvalue-reference-and-why-you http://kholdstare.github.io/technical/2013/11/23/moves-demystified.html http://en.cppreference.com/w/cpp/language/constexpr http://blog.smartbear.com/c-plus-plus/using-constexpr-to-improve-security-performance-and-encapsulation-in-c/ http://www.codeproject.com/articles/417719/constants-and-constant-expressions-in-cplusplus http://cpptruths.blogspot.kr/2011/07/want-speed-use-constexpr-meta.html http://enki-tech.blogspot.kr/2012/09/c11-compile-time-calculator-with.html http://en.wikipedia.org/wiki/c%2b%2b14