템플릿의 확장


template<typename T>


T에 템플릿 클래스 ( Point <int >등 ) 을 인자로 받을 수 있다.

BoundCheckArray<Point<int>>(oparr(50);


특정 템플릿 클래스의 객체를 인자로 받는 일반함수의 정의와 friend 선언


Point<int> 와 같은 템플릿클래스의 자료형을 대상으로도 템플릿이아닌 일반함수의 정의가 가능하며 , 클래스 템플릿 내에서 이러한 함수를 대상으로 friend 선언도 가능하다.


template < typename T>

class Point

{

....

friend Point<int> operator+(const Point<int>& ,const Point<int>& );

}

Point <int> operator+( const Point<int>& pos1, const Point<int>& pos2)

{

return Point<int>(pos1.xpos+pos2.xpos,pos1.ypos+pos2.ypos);

}


클래스 템플릿의 특수화


클래스 템플릿을 특수화하는 이유는 특정 자료형을 기반으로 생성된 객체에 대해 구분이되는 다른 행동양식을 적용하기 위해서이다.


template < typename T>

class SoSimple

{

public:

T SimpleFunc( T num)

{

..    

}

};

template <>

class SoSimple<int> // 클래스템플릿 특수화

{

public :

int SimpleFunc(int num)

};


SoSimple<int> obj1 // 특수화된 템플릿을이용해 객체생성

SoSimple<double> obj2 // 클래스템플릿을이용해 객체생성


클래스 템플릿의 부분특수화


template <typename T1, type name T2> // 특수화되지않은 클래스템플릿

template <char , int > // 전체특수화된 클래스 템플릿

template < typename T1, int > // 부분특수화된 클래스템플릿


객체를 생성할때 , 전체특수화가 부분특수화보다 우선시된다.

ex )

Point <char , int> (A , 2) 일때 , 전체특수화된 클래스템플릿으로 객체생성


템플릿 인자


T 또는 T1 , T2 와같은 문자를 가리켜 템플릿 매개변수라고하며 , 템플릿 매개변수에 전달되는 자료형 정보를 가리켜 템플릿인자라고한다.


템플릿 매개변수에는 변수의 선언이올수있다


template < typename T , int len > 


SimpleArray<int , 5 > i5arr; // len을 5로 치환

SimpleArray<int , 7 > i7arr; // len을 7로 치환


위 객체의 자료형은 서로 다른 자료형이다 . 따라서 두 배열간의 대입등은 불가능하다 . 이렇듯 템플릿 매개변수에 값을 전달받을 수 있는 변수를 선언하면 변수에 전달되는 상수를 통해서 서로 다른 형의 클래스가 생성되게 할 수 있다.


템플릿 매개변수는 디폴트값 지정도 가능하다


template < typename T = int , int len = 7>

SimpleArr<> arr;  // 디폴트값을 이용해 객체를 생성할때도 클래스 템플릿을 이용한 객체생성이기때문에 <> 기호를 반드시 추가해야한다.


템플릿과 static


 template < typename T >

void ShowStaticValue(void)

{

static T num = 0;

num +=1;

cout << num << endl;

}


함수 템프릿 내에 지역변수 num 이 static 으로 선언되었을때 , 컴파일러는 함수템플릿을 기반으로 템플릿함수들을 만들어내고 , 지역변수 또한 템플릿 함수별로 각각 존재하게된다.


void ShowStaticValue(void)

{

static int  num = 0;

num +=1;

cout<< num<< endl;

}

void ShowStaticValue(void)

{

static double num = 0;

num +=1;

cout << num<<endl;

}


두 num은 같은 자료형끼리만 num 값을 공유한다.


클래스 템플릿과 static 멤버변수


static 멤버변수는 변수가 선언된 클래스의 객체간 공유가 가능한 변수이다. 이때도 , 템플릿클래스별로 ( 자료형별로 ) static 멤버변수를 유지하게된다.


template<typename T> 와 template <> 를 쓰는시기


템플릿 함수나 템플릿 클래스를 선언할때 , 정의부분에 T가 존재하면 T 에대한 설명을 위해서 <typename T> 의 형태로 덧붙이고 , T 가 존재하지않으면 <>로 간단하게 선언한다.


템플릿 static 멤버변수 초기화와 특수화


tempalte <typename T>

T simpleStaticMem ::mem = 0; // 모든 자료형에대해서 mem값 0으로 초기화

long SimpleStaticMem :: mem = 5; // long 자료형에대해서는 mem값 5로 초기화 ( 특수화 )



함수 템플릿


함수 템플릿은 기능은 결정되어있지만 자료형이 결정되어있지 않아서 템플릿함수를 만들때 자료형을 결정해주어야한다.


template <typename T> // 다음나올 T가 템플릿이라는것을 컴파일러에게 알려주는용도, typename 대신 class 키워드를 사용하기도한다.

T Add ( T num1 , T num2 )

{

return num1+num2;

}

// 함수 템플릿


int num1=2,num2=3;

int num3 = Add<int>( num1,num2 ) ;

// 템플릿 함수 

 

템플릿함수는 자료형당 하나씩만 만들어지며 , 동일한 자료형의 템플릿함수 사용시 만들어졌던 함수가 호출된다.


int num3 = Add(num1 , num2); //템플릿함수를 일반함수처럼 사용할수도있다. 

이때는 전달되는 인자의 자료형을 참조하여 호출될 함수의 유형을 컴파일러가 결정한다.


둘 이상의 Type에 대해 템플릿선언하기


template < class T1 , class T2> 

void ShowData(double num)

{

cout<<(T1)num<<" , "<<(T2)num<<endl;

}


함수 템플릿의 특수화


템플릿 함수의 구성방법에 예외를 둘 필요가있을때 함수템플릿의 특수화를 이용해서 예외를 둘 수 있다.


template < typename T>

T Max ( T a, T b)

{

return a> b ? a : b ;

}

template <> // 다음나올 함수가 함수템플릿의 특수화로 정의한것이라는것을 컴파일러에게 알려줌

char* Max ( char * a , char * b)

{

return strlen(a) > strlen(b) ? a : b ;

}

template <>

const char * Max( const char * a , const char * b) // const 오버로딩

{

return strcmp(a,b)> 0? a : b;

}


클래스 템플릿


기능과 내부의 행동이 모두 동일한데 저장하는 자료형만이 다른경우에 , 클래스 템플릿을 이용할 수 있다.


template < typename T >

class Point

{

private :

T xpos, ypos;

public :

Point (T x=0 , T y= 0) : xpos ( x ) , ypos( y ) // 디폴트값을 선언한다면 클래스 내부에서만 선언한다.

{} ;

void ShowPosition() const // 클래스템플릿에서 일반함수 정의가능

{

cout<<xpos<< ' , '<<ypos<<endl;

}

T SimpleFunc(const T& ref); // 함수외부에서 정의

};

// 클래스 템플릿


template< typename T> // 다음 나올 T가 템플릿이라는것을 컴파일러에게 전달하기위한 선언

T Point :: SimpleFunc(const T& ref)

{

,,,,

}


Point<int>(3,4);

//템플릿 클래스


템플릿클래스의 생성시에는 템플릿함수처럼 자료형 정보를 생략할수 없다.


파일을 나눌때 고려할점


컴파일러가 클래스템플릿으로 템플릿클래스를 만들때 , 클래스템플릿의 모든것을 알아야하는데 , 헤더파일에 선언된 클래스템플릿에 모든정보가 담겨있지않은경우 ( cpp 에 함수의 정의가 담겨있는경우 등 ) 컴파일에러가 발생할 수 있다 . 이 경우에는 헤더파일에 모든 선언및 정의를 담거나 , 템플릿클래스를 만드는 소스코드에 클래스템플릿의 정보가담겨있는 모든 헤더파일과 소스코드를 include 해야한다.



대입연산자의 오버로딩


대입연산자의 대표적인 특성


정의하지 않으면 디폴트 대입연산자가 삽입된다.

디폴트 대입연산자는 멤버 대 멤버의 복사 ( 얕은복사 ) 를 진행한다.

연산자 내에 동적할당을 한다면 , 그리고 깊은 복사가 필요하다면 , 직접 정의해야한다.


이와같이 복사생성자와 비슷한 특성을 가지는데 , 대표적인 차이는 복사생성자는 한 객체의 초기화에 기존에 생성된 객체를 사용할때 호출된다는것 이고 ,대입연산자는 두 객체가 생성및 초기화가 진행된상태일때 호출된다는것이다.


디폴트 대입 연산자의 문제점


연산자내에 동적할당시 , 얕은복사를 했을때 이미 소멸된 메모리를 또 delete 해서 오류가 발생하거나 가리키던 메모리를 해제하지않고 다른 메모리를 참조하여 메모리 누수가 발생하는등의 문제점이 생길 수 있다. 

따라서 생성자내에 동적할당을 하는경우 , 깊은복사를 진행하도록 정의하며 , 메모리 누수가 발생하지않도록, 깊은 복사에앞서 메모리해제의 과정을 거친다.


Person& operator=(const Person& ref)

{

delete name[]; // 메모리 누수를 막기위해 메모리해제

int len = strlen(ref.name)+1;

name = new char[len];

strcpy(name,ref.name);

age = ref.age;

return *this;

}


상속 구조에서의 대입 연산자 호출


유도클래스의 생성자에는 아무런 명시를 하지않아도 기초클래스의 생성자가 호출되지만, 유도클래스의 대입연산자에는 아무런 명시를 하지않으면 기초클래스의 대입연산자가 호출되지않는다. ( 디폴트 대입연산자는 기초클래스의 대입연산자까지 호출한다 ) 따라서 , 따로 명시해주어야한다.


Second& operator= (const Second& ref)

{

First::operator=(ref);

num3=ref.num3;

num4=ref.num4;

return * this;

}


이니셜라이저가 성능향상에 도움이되는이유


이니셜라이저만을 사용한 생성자일경우 , 기존의 객체를 이용하여 객체를 생성했을때 , 복사생성자만이 호출되지만, 

생성자의 몸체부분에서 대입연산을 통해 초기화하면 , 생성자와 대입연산자가 둘다 호출된다.

이는 이니셜라이저를 사용하면 선언과 동시에 초기화가 이루어지는 형태로 바이너리코드가 생성되지만  생성자의 몸체부분에서 대입연산을 통한 초기화를 진행하면 선언과 초기화를 별도의 문장에서 진행하는 형태로 바이너리코드가 생성되기때문이다.


 배열의 인덱스 연산자 오버로딩


C , C++의 기본배열은 경계검사를하지않아 arr[-1] 과 같은 엉뚱한 코드가 만들어질수있다. 이러한 단점을 배열클래스를 이용하여 해결할 수 있다.

[] 연산자를 오버로딩해야하는데 , 연산자의 기본특성상 멤버함수 기반으로만 오버로딩해야한다.

배열은 저장소의 일종이고 저장소에 저장돈 데이터는 유일성이 보장되어야하기때문에 , 저장소의 복사는 대부분 원천적으로 막는것이 좋다 . 

복사생성자와 대입연산자를 private 멤버로 둠으로서 복사와 대입을 막을수있다.


const 함수를 이용한 오버로딩의 활용


operator[]를 const 선언하면 배열을 멤버로 선언하는경우에는 저장 자체가 불가능해지기때문에 문제가 발생하고 , const 선언을 하지않으면 const 함수에서 호출시에 문제가 발생할때, const 함수를 오버로딩하면 이를 해결할 수 있다. ( cosnt 의 선언유무도 오버로딩에 포함된다 )


class BoundCheckIntArray

{

private : 

int * arr;

int  arrlen;

BoundCheckIntArray(const BoundCheckIntArray& arr) {} // 복사생성자를 private선언함으로써 복사를 제한

BoundCheckIntArray& operator= (const BoundCheckIntArray& arr) // 대입연산자를 private 선언함으로서 대입을 제한

public:

BoundCheckIntArray(int len) :arrlen(len)

{

arr = new int [arrlen];

}

int& operator[] (int idx)

{

if(idx<0 || idx>=arrlen) // 인수가 0보다작은지, 혹은 최대크기보다 큰지 확인

{

cout<<"Array index out of bound exception"<<endl;

exit(1);

}

return arr[idx];

}

int operator[] (int idx) const // const 오버로딩

{

if(idx<0 || idx>=arrlen)

{

cout<<"Array index out of bound exception"<<endl;

exit(1);

}

return arr[idx];

}

int GetArrLen () const 

{

return arrlen;

}

~BoundCheckIntArray()

{

delete[] arr;

}

};


void ShowAllData(const BoundCheckIntArray& ref) // [] 연산자를 const 오버로딩했기때문에 오류발생하지않음 

{

int len = ref.GetArrLen();

for(int idx =0; idx<len; idx++)

cout<<ref[idx]<<endl;

}


int main(void)

{

BoundCheckIntArray arr(5);

for(int i =0;i<5;i++)

arr[i] = (i+1)*11; // [] 연산자 오버로딩을이용해 객체를 배열처럼활용

ShowAllData(arr);

return 0;

}


객체의 저장을 위한 배열클래스의 정의


저장의 대상이 객체일경우


Private:

Point * arr; // 객체의 배열

....

int main(void)

{

arr[0] = Point(3,4); // 임시객체를 생성해서 배열요소를 초기화 , 객체의 저장은 객체간의 대입여산을 기반으로한다.

arr[1] = Point(5,6);


return 0;

}


저장의 대상이 객체의 주소값인경우 ( 더 많이쓰임 )


typedef Point * POINT_PTR // Point * 를 POINT_PTR로 치환


private: 

POINT_PTR * arr; //주소값의 배열

...


int main (void)

{

arr[0] = new Point(3,4); // 객체의 주소값을 저장, 주소값을 저장할경우 깊은복사냐 얕은복사냐하는 문제를 신경쓰지않아도된다.

arr[1] = new Point(5,6);

delete arr[0];

delete arr[1];


return 0;

}


new delete 의 연산자 오버로딩


new 연산자가 하는일은


1. 메모리공간의 할당

2. 생성자의 호출

3. 할당하고자하는 자료형에 맞게 변환된 주소값의 형변환


오버로딩이 가능한부분은 1번에 해당하는 메모리공간의 할당부분뿐이다. 나머지는 컴파일러에의해 진행되며 , 수정할 수 없다.


void * operator new (size_t size) // new 오버로딩의 약속

{

void * adr = new char[size]; // char 의 메모리크기가 1바이트 이기때문에 char단위로 메모리공간을 할당

return adr; // 할당한 메모리주소의 반환

}

operator new 함수가 할당한 메모리공간의 주소갚을 반환하면, 컴파일러는 생성자를 호출해서 메모리공간을 대상으로 초기화를 진행하고 , 완성된 객체의 주소값을 클래스의 포인터형( 예시에서는 Point ) 으로 형변환하여 반환한다.


Point * ptr = new Point(3,4);


여기서 new 연산자가 반환하는값은 operator new 함수가 반환하는값이아니라 컴파일러에의해 적절히 형변환이 된 값이다.


delete 연산자 오버로딩


delete ptr;


이처럼 delete 연산자가 호출되면 컴파일러는 먼저 ptr 이 가리키는 객체의 소멸자를 호출하고 , 다음의형태로 정의된 함수에 ptr에 저장된 주소값을 전달한다. 소멸자는 오버로딩된 함수가 호출되기전에 호출이되므로 , 오버로딩된 함수에서는 메모리공간의 소멸을 책임져야한다.


void operator delete ( void * adr)

{       

delete [] adr; // 메모리공간 해제

}


new 연산자와 delete 연산자가 멤버함수임에도 불구하고 객체의 생성전에 호출 가능한이유


operator new 함수와 operator delete 함수는 static 함수 ( 전역함수 ) 이기때문이다 . 두함수는 따로 static 선언을 하지않아도 자동으로 static 함수로 간주된다.


new 연산자를 이용한 배열 할당


void * operator new (size_t size {}

void * operator new[] (size_t size) {}


이처럼 배열할당시 호출되는 함수라는 사실을 제외하고는 둘의 차이가없다. 이는 delete 연산자도 마찬가지다.


void operator delete (void  * adr) {}

void operator delete[] (void * adr){}


포인터 연산자의 오버로딩


-> 포인터가 가리키는 객체의 멤버에접근

* 포인터가 가리키는 객체에 접근


Number * operator->()

{

return this; // 객체의 주소값반환

}

Number& operator*()

{

return *this; // 객체 반환

}


스마트 포인터


스마트포인터는 포인터 역할을하는 객체를 뜻한다. 따라서 구현해야할 대상이다. 잘 정제되어진 라이브러리에서 제공하는 스마트포인터를 활용할 수 있다.


class SmartPtr

{

private:

Point * posptr;

public:

SmartPtr(Point * ptr) : posptr(ptr)

{}

Point& operator*() const

{

return *posptr;

}

Point * operator->() const

{

return posptr;

}

~smartptr()

{

delete posptr;

}

};


int main(void)

{

SmartPtr sptr(new Point(1,3));

sptr->SetPos(10,20);

return 0;

}


스마트포인터에서 가장 중요한사실은 Point객체의소멸을위한 delete연산이 자동으로 이루어진다는사실이다( smartptr 소멸자에서 delete연산을하기때문 )


() 연산자의 오버로딩과 펑터 (functor)


이연산자를 오버로딩하면 객체를 함수처럼 이용할 수 있게된다. 그리고 함수처럼 동작하는 클래스를 가리켜 펑터(functor) 라고하며 , 함수오브젝트라고도 불린다.

펑터는 객체의 동작방식에 유연함을 제공할때 주로사용된다.


class SortRule

{

public:

virtual bool operator()(int num1,int num2) const =0; // 추상클래스로선언 , 순수가상함수로 정의

};


class AscendingSort : public SortRule // 오름차순

{

public:

bool operator() (int num1, int num2) const

{

if(num1>num2)

return true;

else 

return false;

}

};


class DescendingSort : public SortRule // 내림차순

{

public:

bool operator() (int num1, int num2) const

{

if(num1>num2)

reutnr false;

else

return true;

}

};


int main (void)

{

....

void SortData( const sortRule& functor) // functor 에 DescendingSort 객체를넣거나 AscendingSort 객체를 넣어서 함수를 유연하게 사용할 수 있다

{

...

}

}


임시객체로의 자동 형변환과 형변환 연산자(Conversion Operator)


Number num;

num = 30; // 동일하지않은 자료형의 대입연산


이때 , num = Number(30); 이처럼 임시객체가 생성되고 , 대입연산자가 호출된다.


따라서 , A형 객체가 와야할 위치에 B형 데이터 ( 혹은 객체 ) 가 왔을경우 B 형 데이터를 인자로 전달받는 A 형클래스의 생성자호출을 통해서 A형 임시객체를 생성한다.



operator int() // 형변환연산자의 오버로딩 , 객체를 기본자료형으로 형변환 , 반환형을 명시하지않지만 return문에의한 값의반환은 가능하다.

{

return num;

}


Number num1 = 30; // 임시객체생성 , 대입연산자호출

Number num2 = num1+20; // 형변환연산자 호출 -> num1이 정수형데이터로 형변환



+ Recent posts