c++11에서 기초적인 사항들인데 자꾸 까먹게 돼서 참고용으로 소스를 올려 놓는다. 주요 참고 사항은 아래와 같다.
- Template에서 friend 함수 선언: Class 안과 밖에 두 번 선언하는 이유는 여기 참고.
- std::initializer_list를 이용한 객체 초기화
- Move constructor 호출: move semantics를 아래 main()에서와 같이 사용하는 것은 사실 매우 위험하다. Memory Leak 문제가 생길 수 있다. 객체 사용시 temporary 객체에 대해서만 묵시적으로 동작하도록 하는 것이 좋다. RVO(Return Value Optimization)와 std::move에 대해서는 여기 참고.
- Matrix 연산에서 DMatrix C = A*B와 같은 단순한 binary 연산을 할 경우에는 A와 B가 lvalue이므로 copy constructor가 사용되지만, 이 경우 RVO가 적용되므로(컴파일러가 operator*() 함수에서 생성한 local object를 return하면 바로 C에 할당되도록 함) c++11에 도입된 move semantics가 당장 이득이 되는 건 아니다. 하지만 복잡한 연산을 한 번에 수행할 경우에는 move semantics를 사용하게 된다.
- Matrix 연산에서 다중 for-loop을 간소화하기 위해 template meta programming을 사용하는 라이브러리들이 많은데 move semantics를 사용하더라도 그 의미가 줄어들지는 않을듯...
- 참고로, RVO가 적용되는 경우엔 RVO를 사용하는 것이 가장 빠르고, move semantics는 불필요한 memory 할당과 deep copy를 제거해 주므로 copy semantics만 사용하는 것보다는 훨씬 효율적이다.
- 그니까 아직 c++03 사용자라면, c++11 이후 환경에서는 단순히 move constructor와 move assignment operator만 기존의 class에 추가해 주면, 표준 STL 라이브러리들이 move semantic를 지원하기 때문에 대부분의 경우 나머지 일은 컴파일러가 알아서 해 준다. 물론, 아래의 예처럼 operator overloading을 사용하고 있다면 move semantics 버전을 추가해 주어야 한다.
#include <vector> #include <iostream> #include <initializer_list> // uncomment to disable assert() // #define NDEBUG #include <cassert> #define LOG(x) std::cout << "[" << __FILE__ << "(" << __LINE__ << "): " \ << __FUNCTION__ << "()] " << x << "\n" // template class declaration template <typename TD> struct Matrix; // template friend fuction declarations for Matrix class template <typename TD> std::ostream& operator<<(std::ostream &os, const Matrix<TD>& m); template <typename TD> Matrix<TD> operator*(const Matrix<TD>& m, TD f); template <typename TD> Matrix<TD> operator*(Matrix<TD>&& m, TD f); template <typename TD> struct Matrix { Matrix() { LOG("Default Constructor"); } Matrix(std::initializer_list<TD> il) : m_data(il) { LOG("Initializer_list Constructor: 1 dimesion - size : " << il.size()); } Matrix(std::initializer_list<std::initializer_list<TD>> mil) { LOG("Initializer_list Constructor: 2 dimension"); size_t row = mil.size(); size_t col = 0, pcol = 0; for(auto il: mil) { col = il.size(); if(pcol && col != pcol) { LOG("Fatal Error: no. of columns should be same."); assert(col == pcol); } std::vector<TD> tv = il; m_data.insert(m_data.end(), tv.cbegin(), tv.cend()); pcol = col; } LOG("row, col : " << row << ", " << col); } Matrix(const Matrix& m) : m_data(m.m_data) { LOG("Copy Constructor"); } Matrix(Matrix&& m) : m_data(std::move(m.m_data)) { LOG("Move Constructor"); } Matrix& operator=(Matrix& m) { LOG("Copy Assign"); m_data = m.m_data; return *this; } Matrix& operator=(Matrix&& m) { LOG("Move Assign"); m_data = std::move(m.m_data); return *this; } private: std::vector<TD> m_data; friend std::ostream& operator<<<TD>(std::ostream& os, const Matrix& m); friend Matrix operator*<TD>(const Matrix& m, TD f); friend Matrix operator*<TD>(Matrix<TD>&& m, TD f); }; template <typename TD> std::ostream& operator<<(std::ostream &os, const Matrix<TD>& m) { for(auto v : m.m_data) os << v << " "; os << "\n"; return os; } template <typename TD> Matrix<TD> operator*(const Matrix<TD>& m, TD f) { LOG("copy(m,f)"); Matrix<TD> mr(m); for(auto & v : mr.m_data) v *= f; LOG("mr") << mr; // return std::move(mr); // this prevent RVO(copy elision). return mr; } template <typename TD> Matrix<TD> operator*(TD f, const Matrix<TD>& m) { LOG("copy(f,m)"); return operator*(m, f); } template <typename TD> Matrix<TD> operator*(Matrix<TD>&& m, TD f) { LOG("move(m,f)"); Matrix<TD> mr(std::move(m)); for(auto & v : mr.m_data) v *= f; LOG("mr") << mr; // return std::move(mr); // this prevent RVO(copy elision). return mr; } template <typename TD> Matrix<TD> operator*(TD f, Matrix<TD>&& m) { LOG("move(f,m)"); return operator*(m, f); } int main() { using DMatrix = Matrix<double>; DMatrix m1 {1., 2., 3.}; DMatrix m2 = {{1., 2., 3.}, {4., 5., 6.}}; LOG("m1, m2") << m1 << m2; DMatrix m3 = m1; // copy construct LOG("m3, m1") << m3 << m1; DMatrix m4; // default construct m4 = m2; // copy assign LOG("m4, m2") << m4 << m2; m3 = m1 * 2. * 3. * 5.; LOG("m3") << m3; DMatrix m5 = m1 * 2. * 3. * 5.; LOG("m5") << m5; m4 = 2. * 3. * m1 * 5.; LOG("m4") << m4; DMatrix m6 = 2. * 3. * m1 * 5.; LOG("m6") << m6; DMatrix m7; // default construct m7 = std::move(m1); // move assign LOG("m7") << m7; LOG("m1") << m1; DMatrix m8(std::move(m2)); // move construct LOG("m8") << m8; LOG("m2") << m2; }
댓글 없음:
댓글 쓰기