[ 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 상속
방법 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 멤버로 만들었다간 캡슐화 효과가 날아가 버리고 자원 관리에서도 골치 아픈 일이 생길 수 있다.