2016/07/08

c++11 Template 및 Move Constructor


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;
}

댓글 없음:

댓글 쓰기