본문 바로가기
책 정리/Effective C++ 3rd

항목 44. 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자

by ocean20 2020. 3. 24.

1. 템플릿의 비대화

  1) 문제점 : 템플릿을 잘 못쓰면 중복문제로 이진코드의 비대화 가능성이 있다.

  2) 해결방법 : "공통성 가변성 분석" 을 사용한다. 

  3) 공통성 및 가변성 : 함수 측면에서 살펴보면, 두함수에서 공통적으로 사용되는 로직은 따로 함수화 하여, 그 함수를 각각의 함수에서 호출해주는 방식이라고 생각하면 된다.

 

  * 템플릿도 정확히 동일한 방법으로 코드중복을 막으면된다. 예제를 살펴보자.

template<typename T, std::size_t n>  // T타입의 객체를 원소로 하는 n행 n열의 행렬을 나타내는 템플릿
class SquareMatrix {
public :
  void invert();  // 주어진 행렬을 그 저장공간에서 역행렬로 만든다.
};

  *풀이

  - 고정 크기의 정방행렬을 나타내는 클래스 템플릿

  - 템플릿은 T라는 타입 매개변수도 받고, size_t라는 비타입 매개변수도 받는다.

 

SquareMatrix<double, 5> sm1;
sm1.invert();    //SquareMatrix<double, 5>::invert() 를 호출
SquareMatrix<double, 10> sm2;
sm2.invert();    //SquareMatrix<double, 10>::invert() 를 호출

  * 풀이 : 사본의 개수가 2이다. -> 코드 비대화 일으킨다.

  * 해결방법 : 타입(double)은 고정되고 행렬 사이즈만 다르니, 사이즈를 매개변수로 받는 함수를 별도로 만들고, 타입은 고정시키자

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

template<typename T, std::size_t n)
class SquareMatrix: private SquareMatrixBase<T> {
private:
  using SquareMatrixBase<T>::invert;
public:
  …
  void invert() { this->invert(n); } // 암묵적 인라인 호출
};

 

*풀이

  1) 위에서 풀이한것처럼 타입만 고정시키고, 행렬값을 매개변수로 받는 함수를 별도로 작성하였다. 이제 타입만 같다면, invert 사본은 1개만 발생할 것이다.

  2) 43장에서 학습했던 것처럼 기본클래스 템플릿 상속시, 완전특수화 issue 회피하기 위해 "using" or "this->" 키워드를 사용하여 문제를 해결하였다. private 상속도 is -a 관계가 아니라, 특정기능을 빌려오기 위해 사용되었다.


2. 템플릿 매개변수를 함수 매개변수 혹은 클래스 데이터 멤버로 대체

  - 일단, 이정도만 해도 구현의 문제는 해결되었다. 하지만, 해결하지 못한문제들이 여러 남아 있는데 그중하나는, SquareMatrixBase::invert() 자신이 상대할 데이터가 어디있는지 모른다. --> 정방행렬의 메모리 위치를 파생클래스가 기본클래스로 넘겨주면 될것같다.

  1) SquareMatrixBase::invert() 함수가 매개변수로 행렬의 포인터를 받는것이다. 하지만 잘생각해보자. invert()함수처럼 행렬의 시작주소를 갖는 포인터를 필요로 하는 함수가 있다면, 함수들의 매개변수로 포인터가 추가되어야 하므로 비효율적이게 된다.

  2) 따라서, 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,1> <double> 멤버함수를 사용하고 있더라도, 사실상 둘의 타입이 다르기 때문에, <double,5> <double,10> 객체를 날름 받아먹으려고 할때 컴파일러가 가만두지 않는다.

 

3. 타입 매개변수로 생기는 코드비대화

비타입 매개변수만이 비대화의 원인은 아니다. 상당수의 플랫폼에서 int와 long은 이진 표현 구조가 동일하다. int와 long에 대해 인스턴스화되는 템플릿들은 어떤 환경에서는 코드 비대화를 일으킬 수 있다. 비슷한 예가 토입터 타입의 경우이다. 대부분의 플랫폼에서 포인터 타입은 똑같은 이진 표현구조를 갖고 있기 때문에, 포인터 타입을 매개변수로 취하는 동일 계열의 템플릿들은 이진 수준에서만 보면 멤버 함수 집합을 한 벌만 써도 되어야 한다. 타입 제약이 엄격한 포인터를 써서 동작하는 멤버 함수를 구현할 때는 하단에 타입미정 포인터(void*)로 동작하는 버전을 호출하는 식으로 만든다.

 

 

* 이것만은 잊지 말자!

1. 템플릿을 사용하면 비슷비슷한 클래스와 함수가 여러 벌 만들어진다. 따라서 템플릿 매개변수에 종속되지 않은 템플릿 코드는 비대화의 원인이 된다.

2. 비타입 템플릿 매개변수로 생기는 코드 비대화의 경우, 템플릿 매개변수를 함수 매개변수 혹은 클래스 데이터 멤버로 대체함으로써 비대화를 없앨 수 있다.

3. 타입 매개변수로 생기는 코드 비대화의 경우, 동일한 이진 표현구조를 가지고 인스턴스화되는 타입들이 한 가지 함수 구현을 공유하게 만듦으로써 비대화를 감소시킬 수 있다

댓글