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 버전을 추가해 주어야 한다.
#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;
}

댓글 없음:

댓글 쓰기