Hyun2and

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

공부엔 끝이없다/Effective C++

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

혀니앤 2023. 7. 31. 17:01
728x90

 

 

 

 

포인트

템플릿으로 코드 비대화가 생긴다. (암시적이기 때문에)
템플릿 매개변수를 함수 매개변수나 멤버 데이터로 넣음으로써 해결할 수 있다.
동일한 이진 구조(long, int) 에 대해서는 한가지 함수 구현(void)을 공유하게 만든다.

 


정리

코딩 시간 절약, 코드 중복 회피라는 템플릿의 장점을 누리다보면 코드 비대화 (code bloat)가 발생한다.

    -> 코드 비대화 : 똑같은 애용의 코드와 데이터가 여러 벌로 중복되어 이진 파일로 구워진다)

 

코드 비대화를 해결하기 위한 방법

공통성 및 가변성 분석

어렵게 써놓았지만,, 코드 비대화를 해결하기 위해 평소에 코드의 비슷한 부분을 추려서 함수로 빼고, 그 부분이 필요한 곳에 함수들을 호출하도록 바꾸는 과정. 클래스와 템플릿에도 동일한 과정을 거치면 됨

그러나 템플릿은 암시적! 중복되었다는 것을 어떻게 알지? -> 코드를 직접 보고 파악해야 한다

 

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

이런 기본적인 역행렬을 구하는 템플릿이 있다. 비타입 매개변수 n 을 사용한다

 

	SqureMatrix<double, 5> sm1; 
	sm1.invert();

	SqureMatrix<double, 10> sm2; //차이는 n 이 10이라는 것 뿐이다!
	sm2.invert();

이 경우 두 sm1, sm2 인스턴스는 크기 외에는 다른 점이 없으나 두번 생성되게 된다

이 코드를 크기 값 n 을 매개변수로 가지도록 수정하여 템플릿으로 만들면 invert 함수는 한 개의 사본만을 가지게 된다

 

 

template<typename T>
class SqureMatrixBase
{
//직접 인스턴스를 만들어서 호출하지는 않을 것임. 파생 클래스에서 호출하기 위함
protected:
	void invert(std::size_t matrixsize) {};
};

//같은 이유로 private 으로 상속시킨다
template<typename T, std::size_t n>
class SqureMatrix : private SqureMatrixBase<T>
{
public:
//항목 43에서 다루었던 템플릿에서의 호출을 명확하게 하기 위해 this 사용 (or using을 쓰면 됨)
	void invert() { this->invert(n); }
};

Base 를 만들어서 기본 클래스를 만드는 경우가 많다

 

 

아직 남은 문제... 행렬에 데이터는 어떻게 넘겨줄까?

void invert(std::size_t matrixsize) {};
//이렇게 포인터로 데이터를 넘겨줘도 되지만,, 함수가 n 개 늘어날때마다 다 넣어주는건 좀...
void invert(std::size_t matrixsize, T* matrixptr) {};

암만 생각해도 이쯤 되면 이건 아닌 듯합니다 < 

 

template<typename T>
class SqureMatrixBase
{
protected:
	SqureMatrixBase(std::size_t n, T* pMem) :size(n), pData(pMem) {}

	void setDataPtr(T* ptr) { pData = ptr; }
	void invert(std::size_t matrixsize) {};

private:
	// 항목 22 : 데이터 멤버가 선언될 곳은 private 영역임을 명심하자 참고
	std::size_t size;
	T* pData;
};

이렇게 Base의 멤버함수로 행렬 데이터를 넣어준다

 

template<typename T, std::size_t n>
class SqureMatrix : private SqureMatrixBase<T>
{
public:
	SqureMatrix() : SqureMatrixBase<T>(n, 0), pData(new T[n*n]) 
	{
		this->setDataPtr(pData.get());
	}

	void invert() { this->invert(n); }
private:
	boost::scoped_array<t> pData;
};

위의 방법대로 데이터를 파생 클래스에서 저장하고 포인터의 사본을 기본 클래스로 올리는 방법도 있다

아직 boost::scoped_array 를 쓰는 과정에 대해서는 모르겠다. 항목 13(자원 관리에는 객체가 그만) , 55(boost) 참고하라고 함

 

비교 

- 행렬 크기를 지정하여 각각 별도의 인스턴스를 만드는 것

: 컴파일 시점에 행렬 크기가 투입되므로 최적화하기에 좋다

- 크기독립적으로 매개변수 등으로 넘겨 파생클래스들이 공통으로 쓰도록 함

: 한가지 버전의 함수만 존재하므로 실행 코드의 크기가 작아짐 -> 프로그램 작업 세트 크기가 줄어듦 -> 캐시 내의 참조 지역성이 향상됨 -> 프로그램 실행 속도가 더 빨라짐 

 

추가적으로 고려해야할 것

- 객체의 크기 : 크기 독립형 함수들을 기본 클래스에 계속 넣다보면 객체의 전체 크기가 점점 늘어남

- 함수 매개변수 : int, long 을 넘기는 경우는 ? int* / const int* ?

-> T* 포인터를 쓰고, 하단에서 void* 포인터 함수를 호출하도록 동작함 

( ex : C++ 표준 라이브러리 vector, deque, list ) 

 

 

 

+

모르는 게 많아서 정리하는 데에 오래걸린다... 이 방법이 맞는지 계속 고민해보자

 

 

728x90
Comments