[C++] 템플릿

강민석·2022년 10월 19일
0

이것이 C++이다

목록 보기
7/8
post-thumbnail

9.1 클래스 템플릿

  • 기본적인 내용은 2장의 함수 템플릿과 동일하나, 인스턴스 선언시에는 typename을 반드시 기술해야 한다는 점이 다르다.

  • 선언 방법

    template<typename T>
    class Example {
        ...
    }
  • 다음 예제를 통해 클래스 템플릿의 활용을 알아보자.

    #include <iostream>
    using namespace std;
    
    templcate<typename T>
    class CMyArray {
    public:
        explicit CMyArray(int nSize): m_nSize(nSize) {
            m_pData = new T[nSize];
        }
        ~CMyArray() { delete[] m_pData; }
    
        // 복사 생성자
        CMyArray(const CMyArray &rhs) {
            m_pData = new T[rhs.m_nSize];
            memcpy(m_pData, rhs.m_pData, sizeof(T)*rhs.m_nSize);
        }
    
        // 대입 연산자
        CMyArray& operator=(const CMyArray &rhs) {
            if(this == &rhs) {
                return *this;
            }
    
            delete m_pData;
    
            m_pData = new T[rhs.m_nSize];
            memcpy(m_pData, rhs.m_pData, sizeof(T)*rhs.m_nSize);
    
            return *this;
        }
    
        // 이동 생성자
        CMyArray(const CMyArray &&rhs) {
            operator = (rhs); // ??
        }
    
        // 이동 대입 연산자
        CMyArray& operator=(const CMyArray &&rhs) {
            m_pData = rhs.m_pData;
            m_nSize = rhs.m_nSize;
    
            rhs.m_pData = nullptr;
            rhs.m_nSize = 0;
        }
    
        // 배열 연산자
        T& operator[](int Index) {
            if(nIndex < 0 || nIndex >= m_nSize) {
                cout << "ERROR: Array out of index." << endl;
                exit(1);
            }
    
            return m_pData[nIndex];
        }
    
        // 상수화된 배열 연산자
        T& operator[](int Index) const {
            return operator[](nIndex);
        }
    
        int GetSize() { return m_nSize; }
    
    private:
        T *m_pData = nullptr;
        int m_nSize = 0;
    }
    
    int main(int argc, char* argv[]) {
        CMyArray<int> arr(5);
    
        arr[0] = 10;
        arr[1] = 20;
        arr[2] = 30;
        arr[3] = 40;
        arr[4] = 50;
    
        for(int i=0;i<5;++i) {
            cout << arr[i] << ' ';
        }
        cout << endl;
    
        CMyArray<int> arr2(3);
        arr2 = arr;
        for(int i=0;i<5;++i) {
            cout << arr2[i] << ' ';
        }
        cout << endl;
    }
    • 배열 연산자에서는 메모리 오버플로우를 방지하기 위해 오류를 발생시킨다.
    • 단순 대입 연산자는 r-value에 맞춰 메모리를 새롭게 생성하므로 메모리 오버플로우가 발생하지 않는다.

9.1.1 멤버 선언 및 정의

  • 클래스 템플릿에서도 선언과 정의를 분리할 수 있다.
  • 선언부에서 template<typename T>를 매번 선언해야 한다는 단점이 있다.

9.1.2 템플릿 매개변수

// 템플릿 매개변수를 함수처럼 이용할 수 있다.
template<typename T, int nSize>
class Example1 {
public:
    CMyArray() { m_pData = new T[nSize]; }
private:
    T *m_pData = nullptr;
}

// 형식을 여러 개 작성할 수 있고, 매개변수의 기본값을 둘 수도 있다.
template<typename T1 = int, typename T2 = int>
class Example2 {
public:
    CMyArray(T1 p1, T2 p2) {
        data1 = p1;
        data2 = p2;
    }

private:
    T1 data1;
    T2 data2;
}

9.2 템플릿 특수화

  • 특정 형식에 대해서는 전혀 다른 코드를 적용해야할 때가 있다.

9.2.1 함수 템플릿 특수화

  • 다음과 같이 형식에 따라 함수 구현을 나눌 수 있다.

    #include <memory>
    #include <iostream>
    using namespace std;
    
    template<typename T>
    T Add(T a, T b) { return a + b; }
    
    // 두 개의 변수가 모두 char* 형식이면 이 함수로 대체된다.
    template< >
    char* Add(char *pszLeft, char *pszRight) {
        int nLenLeft = strlen(pszLeft);
        int nLenRight = strlen(pszRight);
        char *pszResult = new char[nLenLeft + nLenRight + 1];
    
        strcpy(pszResult, pszLeft);
        strcat(pszResult, pszRight);
    
        return pszResult;
    }
    
    int main(int argc, char* argv[]) {
        int nResult = Add<int>(3, 4);
        char *pszResult = Add<char*>("Hello", "World");
        delete[] pszResult;
    
        return 0;
    }
  • 템플릿을 특수화 할 때, typename을 기술하지 않는것이 관례다.

9.2.2 클래스 템플릿 특수화

  • 함수 템플릿과 기본적인 선언 방법이 동일하다.
  • template< >으로 특정 타입에 대한 클래스 별도 선언.

9.3 클래스 템플릿과 상속

template<typename T>
class Parent {
    ...
}

template<typename T>
class Child: public Parent<T> {
    ...
}

9.4 스마트 포인터

  • 스마트 포인터는 동적 할당한 인스턴스를 자동으로 삭제해준다.
  • 스마트 포인터에는 다음 네 종류가 있다.
    • auto_ptr: 동적 할당한 인스턴스를 자동으로 삭제한다.
    • shared_ptr: 포인팅 횟수를 계수해서 0이 되면 대상을 삭제한다. -> gc랑 비슷한거 아님??
    • unique_ptr: shared_ptr과 달리 한 대상을 한 포이터로만 포인팅한다. 즉, 하나의 소유자만 허용한다.
    • weak_ptr: 하나 이상의 shared_ptr 인스턴스가 소유하는 개체에 접근할 수 있게 하지만, 참조 수로 계산하지 않는다.

9.4.1 auto_ptr

  • 가장 오래된 구형이다.
  • 사용하지 않는것이 바람직하다.
  • 배열을 지원하지 않을 뿐만 아니라 '얕은 복사' 등의 문제가 해결되지 않았기 때문.

9.4.2 shared_ptr

  • 한 대상을 여러 포인터가 바라볼 수 있으며, 포인팅 수가 0이 되면 대상은 자동으로 메모리에서 해제된다.
  • 배열을 삭제하기 위해선, 생성자를 호출할 때 배열 삭제 함수를 등록해야한다.
        shared_ptr<Example> ptr(new Example(3), [](Example *ex) { delete[] ex; })
    • 생성자에 해당 배열 삭제 시 수행될 함수를 추가 할 수 있는것으로 보인다. 람다식 이용하면 편할듯?

9.4.3 unique_ptr

  • 한 대상을 하나의 포인터만 바라볼 수 있다.
  • 그렇지 않을 경우 컴파일 오류가 발생한다.

0개의 댓글