본문 바로가기
프로그래밍언어/C++

[Effective C++] 항목 44~46

by 목가 2018. 4. 10.
반응형

항목 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

댓글