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 버전을 추가해 주어야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | #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; } |