Expression Template

아래 코드는 doublestd::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를 리턴하게 구현합니다. abVector<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];
}

꽤나 길고 구현하기 까다롭지만 오버헤드 없는 벡터 덧셈을 구현할 수 있습니다.