+Containers::template 다시공부(enable_if and SIFNAE 공부 하려다 몰라서)

YP J·2022년 9월 3일
0

ft_container

목록 보기
3/8

정리한거 날아갔다.. 쓰기전에 로그인 확인하자

모두의 코드 SIFNAE 부분 정리하다 이해가 안 되어서
template 부분부터 다시 공부

modoocode.com/219

#include <iostream>

template <typename T>
class Vector
{
    T* data;
    int capacity;
    int length;

    public:
        Vector(int n =1): data(new T[n]), capacity(n), length(0)
        {
        }

        void push_back(T  s)
        {
            if (capacity <= length)
            {
                T * temp = new T [capacity*2];
                for (int i =0; i < length; i++)
                {
                    temp[i] = data[i];
                }
                delete[] data;
                data = temp;
                capacity *= 2;
            }
            data[length] = s;
            length++;
        }

        T operator[](int i)
        {
            return data[i];
        }

        void remove(int x)
        {
            
            for (int i = x+1; i < length; i++)
            {
                data[i-1] = data[i];
            }
            length--;
        }
        void print()
        {
            for (int i = 0; i < length; i++)
            {
                std::cout << "[ " <<data[i] << " ]" ;
            }
            std::cout << std::endl;
        }
        int size()
        {
            return length;
        }

        virtual ~Vector()
        {
            if (data)
            {
                delete[] data;
            }
        }

};


int main()
{
    Vector<int> int_vec;
    int_vec.push_back(2);
    int_vec.push_back(3);

    int_vec.print();

    Vector<std::string> str_vec;
    str_vec.push_back("test1");
    str_vec.push_back("test2");
    str_vec.print();
}
>>
[ 2 ][ 3 ]
[ test1 ][ test2 ]

template <typename T>
class Vector ~ 
  • 는 아래에 정의되는 클래스에 대해 템플릿을 정의하고
  • 템플릿 인자로 T를 받게 되며
  • T는 반드시 어떠한 타입의 이름임을 명시하고 있다.
  • template <typename T> 밑에 클래스가 있으면 클래스 템플릿이고
  • 만약 template <typename T> 밑에 함수가 온다면 함수 템플릿이다.
template <class T>
  • 라고 쓰는 경우도 있는데 이는 정확히 template <typename T> 와 동일하다.
  • class T 라고 해서 T 자리에 꼭 클래스가 와야하는것은 아님
  • int, char 등이 올 수 있다.

되도록이면 template <typename T> 이렇게 쓰자.

  • Q:왜 똑같은 템플릿에 두개의 키워드를 정의하였나??
  • A: C++ 을 처음 만들었던 Bjarne Stroustrup는 처음에 template인자로 class 키워드를 사용 했다. 굳이 새로운 키워드를 만들고 싶지 않아서.
  • 근데 시간이 흘러서 class T 라고 하면 T자리에 꼭 클래스 와야할것 같은 혼동이 와서 typename 이라는 이름을 사용 하기로 했다.

Vector<int> int_vec;
  • 이렇게 정의한 템플릿의 인자에 값을 전달하기 위해서는
  • 위와 같이 <> 안에 전달하려는 것을 명시해 주면 된다.
  • 우리의 Vector 템플릿은 템플릿 인자로 타입을 받게 되는데, 위경우 T에 int 가 전달 된다.

  • 여태까지는 인자로 특정한 '값' 혹은 '객체' 를 전달해왔지만
  • '타입' 그 자체를 전달 한 적은 없었다.
  • 하지만 템플릿을 통해 타입을 전달 할 수있게 된다.
Vector<int>
Vector<std::string>
  • 이렇게 Vector 의 템플릿의 인자에 타입을 전달하게 되면
  • 컴파일러는 이것을 보고 실제 코드를 생성하게 된다.
  • Vector<int> 의 경우 위의 원본 코드에서 T 부분이 int 로 대체 되어서 코드를 찍어 낸다
Vector<int> int_vec;
  • 따라서 위 코드는 Vector 의 T가 int 로 치완 된 클래스의 객체 int_vec을 생성하게 되는 것.

  • 위와 같이 클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는것을 클래스 템플릿 인스턴스화 라고한다.

  • 템플릿이 인스턴스화 되지 않고 덩그러니 있다면 컴파일시에 아무런 코드로 변환 되지 않는다.

  • 즉, 템플릿은 반드시 인스턴스화 되어야지만 비로소 컴파일러가ㅏ 실제 코드를 생성한다.

  • 위와 같이 템플릿에 사용자가 원하는 타입을 템플릿 인자로 전달하면, 컴파일러는 그 인자를 바탕으로 코드를 생성하여 이를 컴파일 한다.


  • 하지만 간혹 일부 타입은 다른 방식으로 처리해야한다. ex) bool
Vector<bool> int_vec;
  • 그냥 템플릿 인자에 bool을 전달하여 bool을 저장하는 벡터로 사용할수도 있을것이다.
  • But 문제가 있는데 C++에서 기본으로 처리하는 단뒤가 1byte 라는 점이다.
  • 즉, bool데이터 형은 1개의 비트 만으로도 충분히 저장할수 있지만,
  • 8비트를 사용해서 1개의 bool값을 저장해야 된다는 뜻이다.
  • 이건 엄청난 메모리 낭비이다.
  • 따라서 우리는 Vector<bool>에 대해서는 따로 처리해 줘야한다.

Template specializtion

  • 따로 처리해주는것을 템플릿 특수화 라한다.
template<typename A, typename B, typename C>
class test {};
  • 위와 같이 클래스 템플릿이 정의 되어 있을때, "아 나는 A가 int 고 C가 double일때 따로 처리하고 싶어~"
template <typename B>
class test<int, B, double> {};
  • "이 나는 B조차도 특수화 하고 싶어"
template <>
class test<int, int, double> {}
  • 와 같이 써주면 된다.
  • 중요한건 전달하는 템플릿 인자가 없더라도 특수화 하고 싶다면
  • template <> 라도 남겨줘야한다.

  • 이제 bool 벡터의 경우.
template <>
class Vector<bool>
{
	~~
}
  • 이렇게 따로 처리해 주면 된다.

자세한건 https://modoocode.com/219 를 참고하자 (C++ 을 1byte씩 처리 하는데 bool은 1비트만 필요하닌까 그 낭비나지 않고 비트단위로 코드 짜는거에 대한 설명)


함수 템플릿

#include <iostream>
#include <string>

template <typename T>
T max(T& a, T& b)
{
    return a>b ? a : b;
}

int main()
{
    int a = 1, b = 2;
    std::cout << "Max (" << a << "," << b << ") ? : " << max(a,b) << std::endl;

    std::string s = "hello", t = "world";
    std::cout << "Max (" << s << "," << t << ") ? : " << max(s,t) << std::endl;
}
>> 
Max (1,2) ? : 2
Max (hello,world) ? : world
  • 신기하게 킆패스를 인스턴스화 했을때 와는 다르게 <> 하는 부분이 없다. 원래대로 라면
max<int>(a,b)
  • 이렇게 했었지만 C++컴파일러가 알아서 a,b 타입을 보고 알아서 max(a,b)를 max<int>(a,b) 로 인스턴스화 해준다.

  • bubble_sort template 로 구현해보기!
#include <iostream>
#include <string>

template <typename T>
class Vector
{
    T* data;
    int capacity;
    int length;

    public:
        // 어떤 타입을 보관 하는지
        typedef T value_type;
        
        // 생성자
        Vector(int n =1): data(new T[n]), capacity(n), length(0)
        {
        }

        void push_back(T  s)
        {
            if (capacity <= length)
            {
                T * temp = new T [capacity*2];
                for (int i =0; i < length; i++)
                {
                    temp[i] = data[i];
                }
                delete[] data;
                data = temp;
                capacity *= 2;
            }
            data[length] = s;
            length++;
        }
        // 임의의 위치 원소에 접근
        T operator[](int i)
        {
            return data[i];
        }

        void remove(int x)
        {
            
            for (int i = x+1; i < length; i++)
            {
                data[i-1] = data[i];
            }
            length--;
        }
        // swap
        void swap(int i, int j)
        {
            T temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }

        void print()
        {
            for (int i = 0; i < length; i++)
            {
                std::cout << "[ " <<data[i] << " ]" ;
            }
            std::cout << std::endl;
        }
        int size()
        {
            return length;
        }

        virtual ~Vector()
        {
            if (data)
            {
                delete[] data;
            }
        }

};

template <typename Cont>
void bubble_sort(Cont& cont)
{
    for (int i =0; i < cont.size(); i++)
    {
        for (int j= i+1; j<cont.size(); j++)
        {
            if (cont[i] > cont[j])
            {
                cont.swap(i,j);
            }
        }
    }
}


int main()
{
    Vector<int> int_vec;
    int_vec.push_back(3);
    int_vec.push_back(333);
    int_vec.push_back(33);
    int_vec.push_back(3333);
    int_vec.push_back(33333);
    int_vec.push_back(311);

    std::cout << "정렬 이전 ---- " << std::endl;
    int_vec.print();

    std::cout << " 정렬 이후 ---- " << std::endl;
    bubble_sort(int_vec);
    int_vec.print();

}
>>
정렬 이전 ---- 
[ 3 ][ 333 ][ 33 ][ 3333 ][ 33333 ][ 311 ]
 정렬 이후 ---- 
[ 3 ][ 33 ][ 311 ][ 333 ][ 3333 ][ 33333 ]

bubble_sort(int_vec);
  • 이렇게 함수 호출하면 컴파일러는 인자로 전달된 객체의 타입을 보고 알아서 인스턴스화 한 뒤에 컴파일 하게 된다.
  • 위의 경우엔 int_vec 이 Vector<int> 타입 이므로,
  • Vector<int> 가 전달된다.
cont.size();
cont[i] // operator[]
cont.swap(i,j);
  • size(), operator[], swap() 등이 Cont로 전달된 타입에 저러한 것들이 정의 되어 있지 않으면 에러가 발생한다.

  • 그 이유는

  • 프로그램이 실행되었을때 오류가 나는게 아니라 컴파일 할때 발생하는 에러이다.

  • 왜냐면 컴파일시에 모든 템플릿을 실제 코드로 변환 하여 실행 하기 때문이다.

  • 이와 같이 컴파일 시에 모든 템플릿들이 인스턴스화 된다는 사실을 가지고 또 여러가지 흥미로운 코드를 짤수 있는데

  • 이러한 방식을 템플릿 메타프로그래밍 이라고 한다.


  • 만약 버블 정렬을 역순으로 하고 싶을땐 ??

    • 방법1 : bubble_sort2 를 만들어서 부등호 방향을 반대로 바꿔준다.

    • 방법2: operator> 를 오버로딩해서 원하는 방식으로 만들어준다.

    • 방법3: cont[i] 와 cont[j] 의 비교를 > 로 하지 말고 특정 함수에 넣어서 전달한다.

  • 방법2:

struct customClass {
  // ..

  bool operator<(const customClass& c) {
    // Do something
  }
};

Vector<customClass> a;
bubble_sort(a);
  • operator< 을 오버로딩 하는법 그러나

  • 예를 들어서 int 나 string 은 이미 내부적으로 operator< 가 정의되어 있기 때문에 이를 따로 오버로딩을 할 수 없습니다.

  • 방법3:

함수 객체의 도입 (Function Object - Functor)

template <typename Cont, typename Comp>

void bubble_sort(Cont& cont, Comp& comp) {
  for (int i = 0; i < cont.size(); i++) {
    for (int j = i + 1; j < cont.size(); j++) {
      if (!comp(cont[i], cont[j])) {
        cont.swap(i, j);
      }
    }
  }
}
  • 위 함수는 기존의 bubble_sort 와는 달리
  • 아예 Comp 라는 클래스를 템플릿 인자로 받고, 함수 자체도 Comp 객체를 따로 받습니다.
if (!comp(cont[i], cont[j]))
  • 이 if 문에서 마치 함수를 호출하는 것 처럼 사용되는데,

  • cont[i] 와 cont[j] 를 받아서 내부적으로 크기 비교를 수행한 뒤에 그 결과를 리턴하고 있습니다.

  • 한 가지 중요한 사실은 comp 는 함수가 아니라 객체 이고,

  • Comp 클래스에서 () 연산자를 오버로딩한 버전입니다.


#include <iostream>
#include <string>

template <typename T>
class Vector
{
    T* data;
    int capacity;
    int length;

    public:
        // 어떤 타입을 보관 하는지
        typedef T value_type;
        
        // 생성자
        Vector(int n =1): data(new T[n]), capacity(n), length(0)
        {
        }

        void push_back(T  s)
        {
            if (capacity <= length)
            {
                T * temp = new T [capacity*2];
                for (int i =0; i < length; i++)
                {
                    temp[i] = data[i];
                }
                delete[] data;
                data = temp;
                capacity *= 2;
            }
            data[length] = s;
            length++;
        }
        // 임의의 위치 원소에 접근
        T operator[](int i)
        {
            return data[i];
        }

        void remove(int x)
        {
            
            for (int i = x+1; i < length; i++)
            {
                data[i-1] = data[i];
            }
            length--;
        }
        // swap
        void swap(int i, int j)
        {
            T temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }

        void print()
        {
            for (int i = 0; i < length; i++)
            {
                std::cout << "[ " <<data[i] << " ]" ;
            }
            std::cout << std::endl;
        }
        int size()
        {
            return length;
        }

        virtual ~Vector()
        {
            if (data)
            {
                delete[] data;
            }
        }

};

template <typename Cont>
void bubble_sort(Cont& cont)
{
    for (int i =0; i < cont.size(); i++)
    {
        for (int j= i+1; j<cont.size(); j++)
        {
            if (cont[i] > cont[j])
            {
                cont.swap(i,j);
            }
        }
    }
}

template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp)
{
    for (int i = 0; i < cont.size(); i++)
    {
        for (int j = i+1; j < cont.size(); j++)
        {
            if(!comp(cont[i], cont[j]))
                cont.swap(i,j);
        }
    }
}

struct Comp1
{
    bool operator()(int a, int b)
    {
        return a > b;
    }
};
struct Comp2
{
    bool operator()(int a, int b)
    {
        return a < b;
    }
};

int main()
{
    Vector<int> int_vec;
    int_vec.push_back(3);
    int_vec.push_back(333);
    int_vec.push_back(33);
    int_vec.push_back(3333);
    int_vec.push_back(33333);
    int_vec.push_back(311);

    std::cout << "정렬 이전 ---- " << std::endl;
    int_vec.print();

    Comp1 comp1;
    bubble_sort(int_vec, comp1);
    
    std::cout << " 내림차순 정렬 이후 ---- " << std::endl;
    int_vec.print();

    Comp2 comp2;
    std::cout << " 오름차순 정렬 이후 ---- " << std::endl;
    bubble_sort(int_vec, comp2);
    int_vec.print();

}
>>
정렬 이전 ---- 
[ 3 ][ 333 ][ 33 ][ 3333 ][ 33333 ][ 311 ]
 내림차순 정렬 이후 ---- 
[ 33333 ][ 3333 ][ 333 ][ 311 ][ 33 ][ 3 ]
 오름차순 정렬 이후 ---- 
[ 3 ][ 33 ][ 311 ][ 333 ][ 3333 ][ 33333 ]

struct Comp1 {
  bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
  bool operator()(int a, int b) { return a < b; }
};
  • 일단 위 두 클래스를 살펴보도록 합시다.
  • Comp1 과 Comp2 모두 아무 것도 하지 않고 단순히 operator() 만 정의하고 있습니다.
if (!comp(cont[i], cont[j]))
  • 그리고 이 Comp1 과 Comp2 객체들은 bubble_sort 함수 안에서마치 함수인양 사용되지요.
  • 이렇게, 함수는 아니지만 함수 인 척을 하는 객체를 함수 객체 (Function Object), 혹은 줄여서 Functor 라고 부릅니다.
  • 이 Functor 덕분에, bubble_sort 함수 내에서 두 객체간의 비교를 사용자가 원하는 대로 할 수 있게 되지요.

즉, comp() 는 함수가 아니라 객체이고 비교 연산은 comp의 () 연산자 오버로딩에서 이루어지는것이다.


template <typename Cont>
void bubble_sort(Cont& cont)
  • 따라서 사용자들은 입맛에 맞게, 보통의 < 연산자로 비교를 수행하는 위 bubble_sort 함수를 사용하거나
template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp)
  • 특수한 경우에 따로 비교 하는 것을 직접 수행하고 싶은 경우에 Comp 객체를 받아서 비교를 수행하는 새로운 bubble_sort 함수를 사용할 수 있습니다.

실제로 c++ 에서 sort 함수에 대해

template <class RandomIt>
void sort(RandomIt first, RandomIt last);
  • 첫 번째 버전으로 템플릿 인스턴스화 되서 함수가 호출되겠지요
template <class RandomIt, class Compare>
void sort(RandomIt first, RandomIt last, Compare comp);
  • 두 번째 버전으로 템플릿 인스턴스화 되서 함수가 호출되고

  • Q: 만약 C였다면?

  • A: 일단 원하는 클래스를 받는 다는 생각 자체가 불가능하기 때문에 (template 이 없으니까) functor 는 꿈도 못 꾸었겠지요. 대신에, 비교 작업을 수행하는 함수의 포인터를 받아서 이를 처리하였을 것입니다.

  • Q: 그렇다면 뭐가 더 나은 방법일까요? Functor? 아니면 구닥다리 함수 포인터?

  • A: 이미 예상하셨겠지만, Functor 를 사용하는것이 여러 모로 훨씬 편리한 점이 많습니다.

    • 일단, 클래스 자체에 여러가지 내부 state 를 저장해서 비교 자체가 복잡한 경우에도 손쉽게 사용자가 원하는 방식으로 만들어낼 수 있습니다.
    • 뿐만 아니라, 함수포인터로 함수를 받아서 처리한다면 컴파일러가 최적화를 할 수 없지만,
    • Functor 를 넘기게 된다면 컴파일러가 operator() 자체를 인라인화 시켜서 매우 빠르게 작업을 수행할 수 있습니다.
  • 실제로 C 의 qsort 와 C++ 의 표준 sort 함수를 비교한다면 C++ 버전이 훨씬 빠릅니다.
  • 왜냐하면 C 의 qsort 는 비교를 수행하기 위해 매번 함수를 호출시켜야 하지만,
  • C++ 버전의 경우 그 함수를 인라인화 시켜버리면 되기 때문이지요. (함수 호출 필요 없음)

타입이 아닌 템플릿 인자 (non-type template arguments)

~ 부턴 나중에 https://modoocode.com/219

profile
be pro

0개의 댓글