템플릿과 일반화 프로그래밍 📏

안녕하세요! 두두코딩 입니다 ✋
오늘은 Effective C++ 항목 44에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

템플릿 비대화 문제

템플릿을 잘못사용할 경우 중복문제로 인해 binary code가 비대화될 가능성이 있다. 이를 해결하는 방법으로는 공통성 및 가변성 분석을 사용한다

Tip 공통성 가변성 부분을 분리하는 방법 즉, 변하는 부분과 변하지 않는 부분을 분리해 의존도를 최소화 시키는 방법이다. 디자인 패턴 중에 일부분..

템플릿을 사용할 때, 코드 중복이 발생하는 상황은 아래와 같다.

// T 타입의 객체를 원소로 하는 n행 n열의 행렬을 나타내는 템플릿이다.
template<typename T, std::size_t n>
class SquareMatrix {
public:
  // n x n 행렬을 역행렬로 만들어 줌.
  void invert();
};

해당 코드를 수행할 경우 아래와 같은 케이스가 발생할 수 있다.

int main() {
  SquareMatrix<double, 5> sm1;
  sm1.invert();

  // SquareMatrix<double, 5>::invert() 함수 호출

  SquareMatrix<double, 10> sm2;
  sm2.invert();

  // SquareMatrix<double, 10>::invert() 함수 호출
}

위 코드와 같이, 동일한 invert 동작을 수행하는데, 코드가 타입별로 생겨 2중으로 문제가 될 수 있다. 해당 문제를 해결하기 위해서는 타입은 고정시키되, 행렬 사이즈를 전달받는 함수를 별도로 만들어야 한다.

template<typename T>
class SquareMatrixBase {
protected:
  // 매개변수를 받는 별도의 함수
  void invert(std::size_t matrixsize);

};

// private 상속 주목.. 구성으로 만든 것임.
template<typename T, std::size_t n)
class SquareMatrix: private SquareMatrixBase<T> {
private:
  using SquareMatrixBase<T>::invert;
public:
  
  void invert() { this->invert(n); }
};

위의 코드와 같이, SquareMatrixBase 에서 타입을 전달받아 invert함수를 구축해둔다. 이후 매개변수로 행렬의 사이즈를 전달받도록 변경한다.

위와 같이 구성할 경우 invert의 사본은 오직 1개만 생기기 때문에 코드의 비대화를 막을 수 있다. 또한 여기서 주목할 점은 완전특수화 issue를 피하기 위해 “this”를 활용했다. (using 을 사용해도 된다.) 또한, private 상속을 받아 구성으로 만들어, 기능만 사용할 수 있도록 한다.

실제 행렬 데이터 처리

위와 같이 구성할 경우 코드의 비대화 문제를 해결할 수 있다. 하지만, 아직 완벽하게 해결된 것이 아니다. SquareMatrixBase::invert()는 현재 매개변수로 n (행렬의 크기) 값만 전달받도록 되어져 있다. 즉, 실제 데이터가 어디있는지 모르는 문제가 있다.

이럴 경우, 2가지 방법으로 해결 가능하다.

  1. 정방행렬의 메모리 위치를 파생클래스가 기본 클래스로 넘겨준다.

SquareMatrixBase::invert() 함수가 매개변수로 행렬의 포인터를 받도록 만드는 것이다. 하지만, invert 함수와 같이 행렬의 시작 주소를 갖는 포인터를 필요로 하는 함수 (e.g. 행렬의 덧셈 등) 과 추가적으로 포인터를 통해 행렬을 넘겨주는 매개변수를 추가해야된다. 이건 너무 코드상 비효율이 발생한다.

  1. Base 자체적으로 포인터를 저장하는 구조
template<typename T>
class SquareMatrixBase{
protected:
  SquareMatrixBase(std::size_t n, T *pMem) : size(n), pData(pMem) {}
  void setDataPtr(T*ptr) { pData = ptr; }
private:
  std::size_t size;
  T *pData;
};

template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBAse<T> {
public:
  SquareMatrix() : SquareMatrixBase<t>(n, data) {}
  
private:
  // 동적할당도 가능..
  T data[n*n];
};

위 코드와 같이, 파생클래스에서 데이터를 직접 넘겨주는 방법을 선택할수도 있다.

위와 같은 방법으로 3가지 장점을 얻을 수 있다.

  1. SquareMatrix에 속해있는 멤버함수 중 기본클래스가 단순 인라인 함수 호출 가능하게 함.
  2. 기본클래스의 사본 하나만 공유하면 됨
  3. <double, 5> 와 <double,10>은 다른 타입임으로 컴파일러가 구별하기 쉬움.

To Sum Up

  1. 템플릿을 사용하면 비슷비슷한 클래스와 함수가 여러개 만들어짐. 이것이 비대화의 원인
  2. 비타입 템플릿 매개변수로 생기는 코드 비대화는 클래스 데이터 멤버로 대체함으로 비대화 없앨 수 있음
  3. 타입 매개변수로 생기는 코드 비대화 경우 동일한 이진 표현 구조를 가지고 인스턴스화 되는 타입들이 공유하도록 만들면 됨