[C++] Expression Template
아래 코드는 double
형 std::array
의 래퍼인 Vector
클래스를 만들고 덧셈 연산을 오버로딩으로 정의한 코드입니다.
#include <iostream> #include <array> template <size_t N> class Vector { std::array<double, N> x; public: Vector() { std::cout << "constructed w/o init. val." << std::endl; } Vector(const double c) { for (size_t i = 0; i < N; i++) x[i] = c; std::cout << "constructed w/ init. val. " << c << std::endl; } ~Vector() { std::cout << "destructed" << std::endl; } double operator[](const size_t idx) const { return x[idx]; } double &operator[](const size_t idx) { return x[idx]; } }; template <size_t N> Vector<N> operator+(const Vector<N> &a, const Vector<N> &b) { Vector<N> res; for (size_t i = 0; i < N; i++) res[i] = a[i] + b[i]; return res; } int main(void) { Vector<20> a(3), b(5), c(6), d; d = a + b + c; }
실행해보면 constructed가 6번, destructed가 6번 뜰 겁니다. 분명 Vector
변수를 4개만 선언했는데 왜 6번이나 뜰까요? 바로 a+b
와 (a+b)+c
의 결과를 저장하는 임시 객체가 필요하기 때문입니다. 클래스 크기가 작으면 연산 과정에서 임시 객체가 생겼다 사라져도 괜찮지만 클래스 크기가 커지면 성능 저하가 걷잡을 수 없을 정도가 됩니다.
이걸 해결해봅시다. 먼저 Vector
클래스가 std::array
말고도 다양한 컨테이너의 래퍼로 작동하도록 템플릿 인자를 하나 더 줍니다. 그리고 두 컨테이너의 합을 나타내는 클래스 VectorSum
을 정의하고, Vector
의 덧셈은 VectorSum
을 컨테이너로 가지는 Vector
를 리턴하게 구현합니다. a
와 b
가 Vector<N, std::array<double, N>>
변수이면 a+b
의 자료형은
Vector<N, VectorSum<std::array<double, N>, std::array<double, N>>>
이 됩니다. a+b
의 결과를 저장하는 임시 객체가 만들어지긴 하지만, 실제 원소의 덧셈은 임시 객체 단계에서 수행하지 않습니다. 실제 덧셈은 임시 객체를 변수에 대입할 때 이루어집니다. 코드로 보시죠.
#include <iostream> #include <array> template <size_t N, typename T=std::array<double, N>> class Vector { T container; public: Vector() {} Vector(const double c) { for (size_t i = 0; i < N; i++) container[i] = c; } Vector(const T &c) : container(c) {} ~Vector() {} double operator[](const size_t idx) const { return container[idx]; } double &operator[](const size_t idx) { return container[idx]; } const T &cont(void) const { return container; } template <typename S> Vector &operator=(const Vector<N, S> &other) { for (size_t i = 0; i < N; i++) container[i] = other[i]; return *this; } }; template <typename T1, typename T2> class VectorSum { const T1 &left; const T2 &right; public: VectorSum(const T1 &a, const T2 &b) : left(a), right(b) {} double operator[](const size_t idx) const { return left[idx] + right[idx]; } }; template <size_t N, typename T1, typename T2> Vector<N, VectorSum<T1, T2>> operator+(const Vector<N, T1> &a, const Vector<N, T2> &b) { return Vector<N, VectorSum<T1, T2>>(VectorSum<T1, T2>(a.cont(), b.cont())); } int main(void) { Vector<20> a(3), b(5), c(6), d; d = a + b + c; for (size_t i = 0; i < 20; i++) std::cout << d[i]; }
꽤나 길고 구현하기 까다롭지만 오버헤드 없는 벡터 덧셈을 구현할 수 있습니다.