아래 코드는 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];
}
꽤나 길고 구현하기 까다롭지만 오버헤드 없는 벡터 덧셈을 구현할 수 있습니다.