[ Effective C++ ] 항목 44 : 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자

Minsu._.Lighting·2023년 12월 14일
0

[ Effective C++ ] 정리 모음집
" C++ 프로그래머의 필독서, 스콧 마이어스의 Effective C++ 를 읽고 내용 요약 / 정리 "

[핵심]

" 템플릿을 사용할 때 코드 비대화가 일어나지 않게 설계하자! "

  • 템플릿을 사용하면 비슷비슷한 클래스와 함수가 여러 벌 만들어진다, 따라서 템플릿 매개변수에 종속되지 않은 템플릿 코드는 비대화의 원인이 된다!
  • 비타입 템플릿 매개변수로 생기는 코드 비대화의 경우, 템플릿 매개변수를 함수 매개변수 혹은 클래스 데이터 멤버로 대체함으로써 비대화를 종종 없엘 수 있다.
  • 타입 매개변수로 생기는 코드 비대화의 경우, 동일한 이진 표현구조를 가지고 인스턴스화되는 타입들이 한 가지 함수 구현을 공유하게 만듦으로써 비대화를 감소시킬 수 있다.

💡 코드 비대화 방지

📌 코드 비대화

  • 똑같은 내용의 코드와 데이터가 여러개로 중복되어 이진 파일로 생성 되는 것

📌 공통성 및 가변성 분석

  • 함수나 클래스를 설계할 때 만들고 있는 함수, 또는 클래스와 다른 쪽에 공통적인 부분이 있다면 그 부분을 새롭게 옮긴 후 함수의 경우에는 호출을, 클래스의 경우에는 상속이나 객체 합성을 사용해 공통적인 부분을 공유하는 것

  • 템플릿 코드에서 코드 중복은 암시적이다.
    - 소스 코드에는 템플릿이 하나 밖에 없기에...

💡 코드 비대화 예시 - 비타입 매개변수의 경우

template<typename T, size_t n>
class SquareMatrix
{
public:
	...
    void invert();
};

SquareMatrix<double, 5> sm1;
...
sm1.invert();
SquareMatrix<double, 10> sm2;
...
sm2.invert();
  • 템플릿을 포함한 프로그램이 일으키는 코드 비대화의 일반적인 형태 중 하나
    - 두 함수는 행과 열의 크기를 나타내는 상수를 제외 하곤 완전히 똑같다.

📌 코드 비대화 해결 - 행렬의 크기를 매개변수로 받는 별도의 함수를 만들고 호출 하자!

tmeplate<typename T>
class SquareMatrixBase
{
protected:
	...
    void invert(size_t matrixsize);
    ...
};

template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T>
{
private:
	using SquareMatrixBase<T>::invert;
    
public:
	...
    void invert() { this->invert(n); }
};
  • SquareMatrixBase::invert 함수는 파생 클래스에서 코드 복제를 피할 목적으로만 마련한 장치이기 때문에 public이 아닌 protected 멤버로 되어 있음

  • SquareMatrixBase::invert 함수는 인라인 함수로 호출하는데 추가 비용이 없어야 한다

  • this-> 는 기본 클래스의 함수가 가려지는 걸 방지하기 위함이나 using 선언이 되있으므로 불필요한 부분이긴 하다

  • 기본 클래스를 사용한 이유는 온전히 구현을 위함이기 때문에 private 상속

📌 SquareMatrixBase::invert 함수가 자신이 상대할 데이터가 무엇인지 알려주자!

  • 방법 1 - SquareMatrixBase::invert 함수가 매개변수를 하나 더 받도록 만들자!
    - 매개 변수는 행렬 데이터가 들어있는 메모리의 포인터
    - invert 외 함수들이 생겨날 것이고, 결국은 SquareMatrixBase 에게 똑같은 정보를 되풀이 해서 알려주는 꼴이 된다...
    - 너무 좋지 않은 방법!

  • 방법 2 - 행렬값을 담는 메모리에 대한 포인터를 SquareMatrixBase 가 저장하게 하자!

template<typename T>
class SquareMatrixBase
{
protected:
	SquareMatrixBase(std::size_t n, T* pMem)
    : size(n), pData(pMem) {}
    
    void setDataPtr(T* ptr) { pData = ptr; }
    ...
    
private:
	size_t size;
    
    T* pData;
};

template<typename T, size_t n>
class SqaureMatrix : private SquareMatrixBase<T>
{
public:
	SqaureMatrix()
    : SquareMatrixBase<T> (n, data) {}
    ...
private:
	T data(n*n);
};

- 해당 설계는 메모리 할당 방법의 결정 권한을 파생 클래스에게 넘긴다
- 동적 메모리 할당이 필요 없는 객체가 되지만 객체 자체의 크기가 커질 수 있다. 해당 설계가 마음에 들지 않는다면 각 행렬의 데이터를 힙에 두는 방법도 있다.

template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T>
{
public:
	SquareMatrix()
    : SqaureMatrixBase<T> (n, 0),
     pData(new T(n*n))
     { this->setDataPtr(pData.get()); }
     ...
     
private:
	boost::Scoped_array<T> pData;
};

[ 해당 설계의 장점 ]

  • SquareMatrix 에 속해 있는 멤버 함수 중 상당수가 기본 클래스 버전을 호출하는 단순 인라인 함수가 될 수 있다
  • 똑같은 타입의 데이터를 원소로 갖는 모든 정방행렬들이 행렬 크기에 상관 없이 이 기본 클래스 버전의 사본 하나를 공유 한다
  • 행렬 크기가 다른 SquareMatrix 객체는 저마다 고유의 타입을 갖고 있다는 점도 아주 종요하다

[ 공짜는 아니다! ]

  • 코드의 효율성 문제
    - 행렬 크기가 미리 녹아든 상태로 별도의 버전이 만들어지는 invert, 그리고 행렬 크기가 함수 매개변수로 넘겨지거나 객체에 저장된 형태로 다른 파생 클래스들이 공유하는 버전의 invert 함수, 이 둘을 비교하면 전자가 후자보다 좋은 코드를 생성할 가능성이 높다
  • 객체의 크기와 포인터 문제
    - SquareMatrix 객체는 메모리에 생길 때마다 SquareMatrixBase 클래스에 들어 있는 데이터를 가리키는 포인터를 하나씩 갖게되는데, 객체의 크기가 포인터 하나의 크기만큼 낭비가 된다는 것이다. 그렇다고 또 포인터를 protected 멤버로 만들었다간 캡슐화 효과가 날아가 버리고 자원 관리에서도 골치 아픈 일이 생길 수 있다.


💡 타입 매개 변수도 비대화의 원인이 될 수 있다

📌 예시 1 - int와 long

  • 상당수의 플랫폼에서 int와 long의 이진 표현 구조가 같다
    - vector< int >와, vector< long >의 멤버 함수는 똑같이 나온다
    - 코드 비대화

📌 예시 2 - 포인터

  • 대부분의 플랫폼에서 포인터는 똑같은 이진 구조를 가진다
    - 포인터를 매개 변수로 받는 템플릿들은 이진 수준에서만 보면 함수 집합을 한벌만 써도 되어야 함
profile
오코완~😤😤

0개의 댓글

관련 채용 정보