정리한거 날아갔다.. 쓰기전에 로그인 확인하자
모두의 코드 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 ~
template <class T>
되도록이면 template <typename T> 이렇게 쓰자.
Vector<int> int_vec;
Vector<int>
Vector<std::string>
Vector<int> int_vec;
따라서 위 코드는 Vector 의 T가 int 로 치완 된 클래스의 객체 int_vec을 생성하게 되는 것.
위와 같이 클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는것을 클래스 템플릿 인스턴스화
라고한다.
템플릿이 인스턴스화 되지 않고 덩그러니 있다면 컴파일시에 아무런 코드로 변환 되지 않는다.
즉, 템플릿은 반드시 인스턴스화 되어야지만 비로소 컴파일러가ㅏ 실제 코드를 생성한다.
위와 같이 템플릿에 사용자가 원하는 타입을 템플릿 인자로 전달하면, 컴파일러는 그 인자를 바탕으로 코드를 생성하여 이를 컴파일 한다.
Vector<bool> int_vec;
template<typename A, typename B, typename C>
class test {};
template <typename B>
class test<int, B, double> {};
template <>
class test<int, int, double> {}
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)
#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);
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:
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);
}
}
}
}
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; }
};
operator()
만 정의하고 있습니다.if (!comp(cont[i], cont[j]))
즉, comp() 는 함수가 아니라 객체이고 비교 연산은 comp의 () 연산자 오버로딩에서 이루어지는것이다.
template <typename Cont>
void bubble_sort(Cont& cont)
template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp)
실제로 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 를 사용하는것이 여러 모로 훨씬 편리한 점이 많습니다.
인라인화
시켜서 매우 빠르게 작업을 수행할 수 있습니다.
- 실제로 C 의 qsort 와 C++ 의 표준 sort 함수를 비교한다면 C++ 버전이 훨씬 빠릅니다.
- 왜냐하면 C 의 qsort 는 비교를 수행하기 위해 매번 함수를 호출시켜야 하지만,
- C++ 버전의 경우 그 함수를
인라인화
시켜버리면 되기 때문이지요. (함수 호출 필요 없음)
~ 부턴 나중에 https://modoocode.com/219