항목 44 : 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자
* 아무 생각 없이 템플릿을 사용하면 템플릿의 적, 코드 비대화를 초래
- 똑같은 내용의 코드와 데이터가 여러 벌로 중복되어 이진 파일로 구워진다.
* 공통성 및 가변성 분석(Commonallity and variabliity)
1) 함수 : 두 함수를 분석해서 공통적인 부분과 다른 부분을 찾은 후에 공통 부분은 새로운 함수에 옮기고 다른 부분은 원래의 함수에 남겨둔 것이다.
2) 클래스 : 공통 부분을 별도의 새로운 클래스에 옮긴 후, 클래스 상속 혹은 객체 합성을 사용해서 원래의 클래스들이 공통 부분을 공유하도록 한다. 원래의 두 클래스가 제각기 갖고 있는 다른 부분은 원래의 위치에 남아 있게 된다.
3) 템플릿 : 똑같은 방법으로 코드 중복을 막으면 되지만, 템플릿 코드에서는 코드 중복이 암시적
=> 어떤 템플릿이 여러 번 인스턴스화될 때 발생할 수 있는 코드 중복을 감각으로 알아채야 한다 (연습)
template<typename T, std::size_t n> // T 타입의 객체를 원소로 하는 nxn 행령 class SquareMatrix { public: ... void invert(); // 역행렬로 만드는 함수 }; |
- T : 타입 매개변수
- size_t : 비타입 매개변수 (non-type-parameter)
=> C++ 에서만 인정, 정수형orBool형, 상수만 사용, 디폴트 매개변수 사용가능
SquareMatrix<double, 5> sm1; // 5x5 행렬 sm1.invert(); SquareMatrix<double, 10> sm2; // 10x10 행렬 sm2.invert(); |
- size_t 인자만 다르지만, 2개의 invert() 인스턴스 생성 => 코드 비대화
template< typename T> class SquareMatrixBase { protected : // 파생 클래스에서 코드 복제를 피할 목적 voud Invert( size_t matrixSize ); };
template< typename T, size_t n > class SquareMatrix : private SquareMatrixBase< T > // 구현을 돕기 위한 것이기 때문에 Private 상속 { private: using SquareMatrixBase<T>::invert // 기본 클래스의 invert 호출을 위해 public : void Invert() // 함수의 호출에 드는 추가 비용을 없애기 위해 인라인 함수로 선언 { this->Invert( n ); // using 사용으로 this 포인터는 삭제해도 무방 } }; |
- 같은 원소 타입의 정방행렬이 사용하는 기본 클래스 버전의 invert() 함수는 1개
* 기본 클래스의 invert() 함수는 자신이 사용할 행렬 Data를 알수 없다는 문제가 존재
=> 타입정보를 파생 클래스에서 기본 클래스로 전달
1) SquareMatrixBase::Invert 함수가 매개변수(행렬 데이터가 들어 있는 메모리 덩어리의 시작주소를 가리키는 포인터)를 하나 더 받도록 만드는 것 => 비효율
2) 행렬 값을 담은 메모리에 대한 포인터를 SqaureMatrixBase가 저장하게 하는 것
template< typename T > class SquareMatrixBase { protected : SquareMatrixBase( size_t n, T* mem ) : size( n ), data( mem ) { }
void SetDataPtr( T* ptr ) { data = ptr; } private : size_t size; T* data; }; template< typename T, size_t n > class SquareMatrix : private SquareMatrixBase< T > { public : SquareMatrix() : SquareMatrixBase< T >( n, data ) { }
private : T data[n*n]; }; |
- 파생 클래스가 동적 메모리 할당이 필요없는 객체가 됨 (객체 자체의 크기가 커짐)
* 특징
- SquareMatrix에 속해 있는 멤버 함수 중 상당수가 기본 클래스 버전을 호출하는 단순 인라인 함수가 될 수 있으며,
- 똑같은 타입의 데이터를 원소로 갖는 모든 정방행렬들이 행렬 크기에 상관없이 이 기본 클래스 버전의 사본 하나를 공유
- 행렬 크기가 다른 SquareMatrix 객체는 저마다 고유의 타입을 갖고 있다.
* 장점
- 행렬 크기가 미리 녹아든(상수) 상태로 별도의 버전이 만들어지는 형태
(장점) 매개변수 컴파일 시점에 투입되는 상수이기 때문에 상수 전파 등의 최적화가 좋음
- 행렬 크기가 함수 매개변수로 넘겨지거나 객체에 저장된 형태로 다른 파생 클래스들이 공유 형태
(장점) 실행 코드의 크기가 작아짐
* 템플릿을 사용하면 비슷비슷한 클래스와 함수가 여러 벌 만들어진다. 따라서 템플릿 매개변수에 종속되지 않은 템플릿 코드는 비대하의 원인이 된다. * 비타입 템플릿 매개변수로 생기는 코드 비대화의 경우, 템플릿 매개변수를 함수 매개변수 혹은 클래스 데이터 멤버로 대체함으로써 비대화를 종종 없앨 수 있다. * 타입 매개변수로 생기는 코드 비대화의 경우, 동일한 이진 표현구조를 가지고 인스턴스화되는 타입들이 한 가지 함수 구현을 공유하게 만듦으로써 비대화를 감소시킬 수 있다. |
항목 45 : "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 테플릿이 직방!
* 포인터를 스마트 포인터로 대신할 수 없는 특징 => 암시적 변환 지원
class Top{ ... }; class Middle : public Top{ ... }; class Bottom : public Middle{ ... }; Top* top1 = new Middle; // Middle* -> Top*로 변환 Top* top2 = new Bottom; // Bottom* -> Top*로 변환 const Top* top3 = top1; // Top* -> const Top*로 변환 |
- 스마트 포인터를 사용할 경우, 별도로 구현을 해줘야 한다
* 멤버 함수 템플릿 (member function template)
- 어떤 클래스의 멤버 함수를 찍어내느 템플릿?
template< typename T > class SmartPtr { public : template< typename U > SmartPtr( const SmartPtr< U >& other ); }; |
- 모든 T 타입은 모든 U 타입에 대해서, SmartPtr<T> 객체가 SmartPtr<U>로부터 생성될 수 있다는 코드
- SmartPtr<U>의 참조자를 매개변수로 받아들이는 생성자가 SmartPtr<T> 안에 들어있기 때문.
=> 일반화 복사 생성자(generalized copy constructor)
* 일반화 복사 생성자
- explicit로 선언되지 않음 (포인터는 암시적으로 그냥 변환이 되니...)
template< typename T > class SmartPtr { public : template< typename U > SmartPtr( const SmartPtr< U >& other ) : heldPtr( other.Get() ) { ... } T* Get() const { return helpPtr; }
private : T* heldPtr; }; |
- T* 타입의 포인터를 U* 타입의 포인터로 초기화를 하여, U*에서 T*로 진행(상속관계)되는 암시적 변환만 가능
=> SmartPtr<T> 일반화 복사 생성자는 호환되는 타입의 매개변수를 받을때만 컴파일 됨.
=> 타입 변화의 제한을 두기 위한 방법
* C++에서는 생성자+소명자+복사생성자+복사대입연산자를 생성
- 클래스의 본사 생성을 모두 control하고 싶다면, 모두 직접 선언이 필요함
template< typename T > class SmartPtr { public : SmartPtr( SmartPtr const& ptr ); // 복사 생성자 template< typename U > SmartPtr( const SmartPtr< U >& other ); // 일반화 복사 생성자 SmartPtr& operator=( SmartPtr const& ptr ); // 복사 대입 연산자 template< typename U > SmartPtr& operator=( SmartPtr< U > const& ptr ); // 일반화 복사 대입 연산자 T* Get const { return helpPtr; } private : T* heldPtr; }; |
* 호환되는 모든 타입을 받아들이는 멤버 함수를 만들려면 멤버 함수 템플릿을 사용한다. * 일반화된 복사 생성 연산과 일반화된 대입 연산을 위해 멤버 템플릿을 선언했다 하더라도, 보통의 복사 생성자와 복사 대입 연산자는 여전히 직접 선언해야 한다. |
항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자
template< typename T > class Rational { public : Rational( const T& numerator = 0, const T& denominator = 1 );
const T GetNumerator() const; const T GetDenominator() const; };
template< typename T > const Rational<T> operator*( const Rational<T>& lhs, const Rational<T>& rhs ) { ... }
Rational< int > oneHalf( 1, 2 ); Rational< int > result = oneHalf * 2; // 컴파일 안됨. |
- 컴파일러는 operator*라는 이름의 템플릿으로부터 인스턴스화할 함수를 검색
- Rational<T> 타입의 매개변수를 두 개 받아들이는 operator*라는 이름의 함수를 인스턴스로 만들어야 한다
* 템플릿 인자 추론 (template argument deduction)
- 암시적 타입 변환이 고려되지 않는다.
- 프렌드 함수 이용 (함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있다)
=> Rational<int> 타입의 인스턴스가 생성되면 프렌드 함수 operator*도 자동으로 선언
- Rational 내부에서 선언된 코드로, 정의(구현) 부분이 없어 링크 단계에서 컴파일 에러 발생가 발생함으로, operator* 함수의 정의를 선언부와 함께 써주자 (inline 함수형식)
template< typename T > class Rational { public : Rational( const T& numerator = 0, const T& denominator = 1 ); friend const Rational<T> operator*( const Rational<T>& lhs, const Rational<T>& rhs ) { return Rational( lhs.GetNumerator() * rhs.GetNumerator(), lhs.GetDenominator() * rhs.GetDenominator() ); } template< typename T > const Rational<T> operator*( const Rational<T>& lhs, const Rational<T>& rhs ) { ... } }; |
* 모든 매개변수에 대해 암시적 타입 변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런 함수는 클래스 템플릿 안에 프렌드 함수로서 정의하자. |
'프로그래밍언어 > C++' 카테고리의 다른 글
[Effective C++] 항목 47~49 (0) | 2018.04.10 |
---|---|
[Effective C++] 항목 41~43 (0) | 2018.04.10 |
[Effective C++] 항목 38~40 (0) | 2018.04.10 |
[Effective C++] 항목 35~37 (0) | 2018.04.10 |
[Effective C++] 항목 29~31 (0) | 2018.04.10 |
댓글