일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- UE4
- operator new
- Smart Pointer
- 루아
- more effective c++
- exception
- virtual function
- 상속
- effective stl
- lua
- c++
- Vector
- resource management class
- Effective c++
- 영화 리뷰
- 오블완
- 예외
- reference
- 비교 함수 객체
- 암시적 변환
- 다형성
- 참조자
- 반복자
- 메타테이블
- 언리얼
- 스마트 포인터
- 티스토리챌린지
- 게임
- 영화
- implicit conversion
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 44. 템플릿 코드 비대화 회피하기 본문
728x90
항목 44. 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자
템플릿의 장단점
- 장점
- 코딩 시간 절약
- 코드 중복 회피
- 단점
- 코드 비대화
- 거의 똑같은 내용의 코드와 데이터가 중복되어 이진파일로 구워진다.
- 코드 비대화
템플릿 코드 비대화 회피 방법
- 공통성 및 가변성 분석(commonality and variability analysis)
- 공통 부분을 별도의 클래스로 옮긴 후 클래스 상속 혹은 객체 합성으로 공유한다.
- 이미 이런 분석을 항상 사용해 왔을 것이며, 템플릿에도 똑같이 적용하여 코드 중복을 막으면 된다.
- 하지만... 템플릿은 코드 중복이 암시적이라 감각적으로 알아차리는 수밖에 없다.
템플릿 코드 비대화 해결법을 코드로 알아보자
template<typename T, std::size_t n>
class SquareMatrix // 정방행렬
{
public:
...
void invert(); // 역행렬로 만들기
};
// 사본을 두 개 만든다.
// invert 코드가 거의 동일함에도 중복 생성된다!!
SquareMatrix<double, 5> sm1; // 5x5 행렬
sm1.invert();
SquareMatrix<double, 10> sm2; // 10x10 행렬
sm2.invert();
- 코드 중복을 제거한 버전
- 이름 가리기 문제 (항목 33 참조)
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; // invert 가려짐 문제 해결(항목 33 참조)
private:
void invert() { this->invert(n); } // 중복 코드 제거, 암시적 인라인
};
- 수정된 사항
- 모든 정방행렬은 오직 한 가지 SquareMatrixBase 클래스를 공유하게 되므로 코드 중복이 제거된다.
- 함수 호출에 드는 추가 비용을 없애기 위해 암시적 인라인 함수로 구현했다.
- 기본 클래스가 파생 클래스의 구현을 돕기 위함이라는 걸 명확히 하기 위해 private 상속을 사용했다.
- 하지만 이 방식으로 구현하려면 문제가 있다.
- 역행렬을 만들기 위해서는 실제 행렬의 데이터가 필요하나, 부모 클래스의 invert 함수에서 실제 행렬 데이터에 접근할 방법이 없다.
- 부모 클래스로 실제 행렬 데이터 넘기기
- 접근해야 하는 데이터를 부모 클래스 함수의 매개변수로 넘겨준다.
- 하지만 invert처럼 행렬 크기에 상관없는 동작방식의 함수가 매우 많다면?
- 이들 모두에 매개변수를 추가하는 건 번거롭고 SquareMatrixBase에 같은 정보를 계속 넘겨주는 꼴이다.
- 접근해야 하는 데이터의 포인터를 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: std::size_t size; // 행렬 크기 T* pData; // 행렬 값에 대한 포인터 }; template<typename T, std::size_t n> class SqaureMatrix : private SquareMatrixBase<T> { public: SquareMatrix() : SquareMatrixBase<T>(n, data) {} ... private: T data[n*n]; };
- 행렬 크기도 저장하지 않을 이유가 없기 때문에 함께 저장했다.
- 이렇게 하여 T가 동일하며, 행렬 사이즈만 다른 경우에는 동일한 부모 클래스를 참조하게 된다.
-
- 접근해야 하는 데이터를 부모 클래스 함수의 매개변수로 넘겨준다.
코드 중복을 제거한 버전의 장단점
- 단점
- 코드 중복을 제거하기 전의 버전이 이후의 버전보다 좋은 코드를 생성할 가능성이 높다.
- 코드 중복 전의 버전에서는, 행렬 크기가 컴파일 시점에 투입되는 상수이기 때문에 상수 전파(constant propagation) 등의 최적화가 먹혀들 여지가 있다.
- 생성되는 기계 명령어에 이 크기 상수 값이 즉치 피연산자(immeditate operand)로 바로 박힐 수 있다.
- 아무 생각 없이 기본 클래스 쪽으로 코드를 옮기다 보면 각 객체의 전체 크기가 늘어나게 된다.
- SquareMatrixBase 클래스에 들어가는 포인터는, 파생 클래스의 데이터의 중복이라 볼 수 있다.
- 즉, SquareMatrix 객체 하나의 크기는 최소한 포인터 하나 크기만큼 낭비된 것이라 할 수 있다.
- 코드 중복을 제거하기 전의 버전이 이후의 버전보다 좋은 코드를 생성할 가능성이 높다.
- 장점
- 실행 코드의 크기가 작아진다.
- 이로 인해 프로그램의 작업 세트(working set, 한 프로세스에서 자주 참조하는 페이지의 집합) 크기가 줄어든다.
- 명령어 캐시 내의 참조 지역성(locality of reference, 프로세스의 메모리 참조가 실행 중에 균일하게 흩어져 있지 않으며 특정 시점 및 특정 부분에 집중된다는 경험적/실험적 특성)도 향상된다.
- 따라서 프로그램의 실행 속도가 빨라질 수 있다.
- 실행 코드의 크기가 작아진다.
- 어떤 효과가 우선일까?
- 정확한 판단을 위해서는 사용 중인 플랫폼 및 대표 데이터 집합에 대해 두 방법을 모두 적용해 보고 결과를 관찰하는 수밖에 없다.
타입 템플릿 매개변수 코드 비대화를 생각해보자
- 현재까지는 비타입 템플릿 매개변수 코드 비대화에 대해서만 살펴보았지만..
- 사실 타입 매개변수 역시 코드 비대화의 원인이 될 수 있다.
- 생각해 볼만한 비타입 템플릿 매개변수 코드 비대화 상황
- 상당수의 플랫폼에서 int와 long은 이진 표현 구조가 동일하다.
- 그러므로 vector<int>와 vector<long>의 멤버 함수는 서로 빼다 박은 듯 똑같이 나올 수 있을 것이다.
- 즉, int 및 long에 대해 인스턴스화되는 템플릿들은 '어떤 환경' 에서는 코드 비대화를 일으킬 수 있다.
- 링커가 똑똑하게 동일 표현구조를 합쳐주기도 하기 때문이다.
- 포인터 타입을 매개변수로 취하는 동일 계열 템플릿들은 이진 수준에서만 보면 멤버 함수 집합을 한 벌만 써도 될 것이다.
- 예컨대 list<int*>, list<const int*>, list<SquareMatrix<long, 3>*> 등.
- 타입 미정(untyped) 포인터(void*)로 동작하는 버전을 구현 및 사용하여 코드 중복을 없앨 수 있을 것이다.
- vector, deque, list 등, C++ 표준 라이브러리에서 실제로 사용하는 방법이다.
- 상당수의 플랫폼에서 int와 long은 이진 표현 구조가 동일하다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 46. 템플릿 클래스 안에 비멤버 함수 두기 (0) | 2024.07.18 |
---|---|
[Effective C++] 45. 멤버 함수 템플릿으로 암시적 변환 지원 (1) | 2024.07.17 |
[Effective C++] 43. 템플릿 부모 클래스의 인터페이스에 접근하기 (0) | 2024.07.15 |
[Effective C++] 42. typename의 두 가지 용법 (0) | 2024.07.12 |
[Effective C++] 41. 템플릿 프로그래밍 (0) | 2024.07.11 |
Comments