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;
}
댓글 없음:
댓글 쓰기