16행: 여기서는 문자열을 대상으로 호출하고 있다. 하지만 그 결과에 대해서는 아무런 의미도 부여할 수 없게 된다. (단순히 주소 값의 비교결과가 반환되므로)


만약에 문자열의 길이비교가 목적이라면 다음의 형태로 템플릿 함수가 구성되어야 의미가 있다.

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

{

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

}


만약, 사전편찬 순서의 비교가 목적이라면...

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

{

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

}


이렇듯 상황에 따라서 템플릿 함수의 구성방법에 예외를 둘 필요가 있는데, 이 때 사용되는 것이 "함수 템플릿의 특수화(Specialization of Function Template)" 이다.




11~16행: 함수 템플릿 Max를 char* 형에 대해서 특수화 하였다.

18~23행: 함수 템플릿 Max를 const char* 형에 대해서 특수화 하였다.


위 예제의 다음 정의는,


template <>

char*~~~

{

...

}


컴파일러에게 다음의 메시지를 전달하는 것이다.


"char* 형 함수는 내가 이렇게 제시를 하니, char* 형 템플릿 함수가 필요한 경우에는 별도로 만들지말고 이것을 써라."



그리고 이 두 '함수 템플릿의 특수화' 정의는 특수화하는 자료형의 정보 <char*>, <const char*> 를 생략한 형태이며, 이를 생햑하지 않고 정의를 하면 다음의 형태가 된다.





특수화하는 자료형 정보를 생략하건 생략하지 않건 그 의미하는 바에 차이가 없으나, 가급적이면 자료형 정보를 명시하는 것이 뜻을 명확히 하는 방법이 된다.


※ 원래는 저렇게 자료형 정보를 표시해야만 했었다.!! 그러므로 저렇게 표시를 하자!! 저렇게 표시만해도 일반 함수인지 아니면 템플릿으로 만들어진 함수인지를 금새 구분하니까~~~!!!

Posted by scii
:

Template

Programming/C++ 2012. 10. 10. 00:20 |

함수 템플릿

- 함수 템플릿은 함수를 만들어 낸다. 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야 한다.


함수 템플릿 이라는 것은 함수를 만드는 도구가 된다. 모형자가 모형을 만드는 도구가 되는 것처럼!! 그리고 모형자가 다양한 색의 모형을 만들어 내는 것처럼, 함수 템플릿도 다양한 자료형의 함수를 만들어 낼 수 있다.


int Add(int a, int b)

{

return a+b;

}


위에 정의한 함수의 자료형을 T로 대신할 수 있다. 


T Add(T a, T b)

{

return a+b;

}


int 형 자료형을 T로 대신했음을 알 수 있는데, 이는 자료형을 결정짓지 않은, 그래서 나중에 T를 대신해서 실제 자료형을 결정하겠다는 뜻이다.


그런데 이것이 전부가 아니다!! 컴파일러에게 다음과 같은 메시지를 전달해야 한다.


"T는 자료형을 결정짓지 않겠다는 의미로 사용한 것이다. 즉, 함수를 만들어 내는 템플릿을 정의하기 위해서 사용된 것이다."

이러한 메시지를 담아야만 함수 템플릿이 완성된다.


template <typename T>

T라는 이름을 이용해서 아래의 함수를 템플릿으로 정의한다는 의미이다.


※ typename 을 대신해서 class 를 사용할 수도 있다.

template <typename T> 선언을 대신해서 template <class T> 를 선언해도 똑같은 의미이다.

그리고, T라는 문자대신 다른 문자를 사용해도 된다.



14행에 Add<int>(15, 20); 라는 구문이 등장한다. 여기서 <int>가 의미하는 바는 "T를 int로 해서 만들어진 Add함수를 호출하라" 라는 의미가 된다.


위 예제에서 함수 템플릿을 기반으로 만들어진 함수는 이렇게 된다.


int Add<int>(int a, int b)

{

return a+b;

}


double Add<double>(double a, double b)

{

return a+b;

}


※ 함수 템플릿으로 한번 함수가 만들어지면, 그 다음에는 만들어진 함수를 호출할 뿐 새로 함수를 만들지는 않는다.

즉, 함수는 자료형당 하나씩만 만들어진다.


14행에서 주석이 설명하는 것처럼 함수 호출하듯이 Add(4, 3) 이렇게 써도 된다. 

왜냐하면, 전달하는 인자의 자료형을 참조하여 호출될 함수의 유형을 컴파일러가 결정하기 때문이다.


함수 템플릿 & 템플릿 함수


# 함수 템플릿


template <typename T>

T Add(T a, T b)

{

return a+b;

}


이것을 가리켜 "함수 템플릿(Function Template)" 이라 한다.

즉, 호출이 가능한 함수가 아닌, 템플릿임을 강조한 것이다.


함수 템플릿 => 함수를 만드는데 사용되는 템플릿




# 템플릿 함수


int Add<int>(int a, int b)

{

return a+b;

}


위의 템플릿을 기반으로 컴파일러가 만들어 내는 이런 유형의 함수들을 가리켜 "템플릿 함수(Template Function)" 이라 한다.

즉, 템플릿을 기반으로 만들어진, 호출이 가능한 함수임을 강조한 것이다.


템플릿 함수 => 템플릿을 기반으로 만들어진 함수


※ 템플릿 함수의 또 다른 표현

- 템플릿 함수는 컴파일러에 의해 성성된 함수이기 때문에 "생성된 함수(Generated Function)" 으로도 불린다.     

템플릿 클래스 역시 "생성된 클래스(Generated Class)" 라고도 불린다.





둘 이상의 형(type)에 대해 템플릿 선언하기


템플릿의 정의에도 다양한 자료형의 선언이 가능할 뿐만 아니라, 둘 이상의 형(type)에 대해서 템플릿을 선언할 수도 있다.



5행: 함수 템플릿의 매개변수 조차도 기본 지료형으로 선언될 수 있다.

7행: 인자로 전달된 num의 값을 T1과 T2로 명시되는 자료형으로 형 변환해서 출력하고 있다.

13~17행: 위의 함수 템플릿은 매개변수 형이 double로 선언되었기 때문에 전달되는 인자를 통해서는 T1과 T2의 자료형을 결정짓지 못한다. 따라서 이러한 경우에는 템플릿 함수의 호출형식을 완전히 갖춰서 호출해야 한다.


※ C++ 에서는 데이터에 소괄호를 묶는 형태로 형 변환을 명령할 수 있다.

ex) int num = (int)3.14;        =>        int num = int(3.14); 

이 두개의 문장은 완전히 일치한다.




template의 다른 예)






Posted by scii
:

C++ 표준 라이브러리에는 string 이라는 이름의 클래스가 정의되어 있다. 클래스의 이름이 의미하는 것처럼, 문자열의 처리를 목적으로 정의된 클래스이며, 이 클래스의 사용을 위해서는 헤더파일 <string> 을 포함해야 한다.


string 클래스를 대체할 수 있는 String 클래스를 구현해 보자~!!


표준 string 클래스의 분석


1. 문자열을 인자로 전달받는 생성자의 정의


2. 생성자, 소멸자, 복사 생성자 그리고 대입 연산자의 정의

- 저장하고자 하는 문자열의 길이가 일정치 않다. 따라서 문자열의 저장을 위한 메모리 공간을 생성자 내에서 동적 할당해야 한다. 

그리고 이로 인해서 소멸자를 정의해야 하며, 깊은 복사를 하는 복사 생성자와 대입 연산자까지 함께 정의하고자 한다.


3. 결합된 문자열로 초기화된 객체를 반환하는 + 연산자의 오버로딩

- + 연산자의 반환 값은 2가지로 생각할 수 있다.

1. 문자열의 주소 값(str1과 str2가 지니고 있는 문자열을 합한 문자열의 주소 값)

2. string 객체(str1과 str2가 지니고 있는 문자열을 합한 문자열을 저장하고 있는 객체)


4. 문자열을 덧붙이는 += 연산자의 오버로딩


5. 내용비교를 진행하는 == 연산자의 오버로딩


6. 콘솔입출력이 가능하도록 <<, >> 연산자의 오버로딩





61행: 주석에 달린 것 처럼 *this = *this+s;

                return *this; 

이렇게 정의해도 똑같다. 이러한 형태의 정의는 간결해 보이고 이해하기도 좋지만, 덧셈의 과정에서 객체가 추가로 생성된다는 단점이 있다.

하지만 컴퓨팅 파워가 좋은 환경이라면 이 정도는 단점이 될 수 없다. 이러한 형태의 구현도 생각해볼 만하다.


※ #include <string> 헤더파일을 포함시키면, 이렇게 직접 구현하지않고 이런식이 되도록 쓸 수 있다.


Posted by scii
:

두 객체의 자료형이 일치할 때에만 대입연산이 가능하다.





32행을 조금 풀어서 쓰면 다음과 같다.


num = Number(30);                            // 1단계. 임시객체의 생성

num.operator=(Number(30));            // 2단계. 임시객체를 대상으로 하는 대입 연산자의 호출


여기서의 핵심은 임시객체의 생성이다. 그리고 이러한 임시객체의 생성을 통해서 대입연산이 진행되는 데에는 다음과 같은 문법적 기준이 존재한다.


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


때문에 위의 예제에서는 'Number형 객체가 와야 할 위치에 int 형 데이터가 와서, int 형 데이터를 인자로 전달받는 Number 클래스의 생성자 호출을 통해서 Number 형 임시객체를 생성한 것' 이다.


이렇듯, 기본 자료형 데이터를 객체로 형 변환하는 것은 적절한 생성자의 정의를 통해서 얼마든지 가능하다. (반대로 객체를 기본 자료형 데이터로 형 변환하는 것도 가능하다)






앞서 정의한 Number 클래스를 대상으로 덧셈연산이 가능하게끔 하는 예제




위 예제에서 정의한 형 변환 연산자는 다음과 같다.


operator int()

{

return num;

}


이렇듯 형 변환 연산자는 반환형을 명시하지 않는다. 하지만 return 문에 의한 값의 반환은 얼마든지 가능하다.

그리고 오버로딩 된 연산자의 이름이 operator+ 이면, + 연산자가 등장했을 때 호출되는 것과 유사하게 operator int 는 다음의 의미로 이해하면 된다.


int 형으로 형 변환해야 하는 상황에서 호출되는 함수이다.


※ int 형 말고도 다른 자료형의 이름이 올 수 있다.


즉, int 형으로 형 변환되어야 하는 상황에서 호출이 되며, 이 때 return 문에 의해 반환되는 값이 int 형으로의 형 변환 결과가 되는 것이다.


Number num2 = num1+20;


num1 객체의 operator int 함수가 호출되어, 이 때 반환되는 값 30과 20의 덧셈연산이 진행되며, 이 연산의 결과로 num2 객체가 생성된 것이다.

'Programming > C++' 카테고리의 다른 글

Template  (0) 2012.10.10
string 클래스의 디자인  (0) 2012.10.09
()연산자 오버로딩과 펑터(Functor)  (0) 2012.10.07
스마트 포인터  (0) 2012.10.04
포인터 연산자 오버로딩  (0) 2012.10.04
Posted by scii
:

함수 호출에 사용되는, 인자의 전달에 사용되는 () 도 연산자이다. 따라서 이 연산자 역시 오버로딩이 가능한 연산자이다.


ex) adder(4, 5); 가 있다면 adder.operator()(4, 5); 이렇게 해석이 된다.




위 예제에서 정의한 Adder 클래스와 같이 함수처럼 동작하는 클래스를 가리켜 '펑터(Functor)' 라 한다.

그리고 '함수 오브젝트(Function Object)' 라고도 불린다.





펑터의 위력 & 활용


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



매개변수 형이 SortRule 의 참조형이므로, SortRule 클래스를 상속하는 AscendingSort 클래스와 DescendingSort 클래스의 객체는 인자로 전달 가능하다. 그리고 SortRule 의 operator() 함수는 virtual 로 선언되었으니, 유도 클래스의 operator() 함수가 대신 호출된다. 

때문에 펑터로 무엇이 전달되느냐에 따라서 정렬의 기준이 바뀌게 된다. 그리고 이것이 펑터를 정의하는 이유이다.

Posted by scii
:

스마트 포인터

Programming/C++ 2012. 10. 4. 13:16 |

스마트 포인터란 말 그대로 똑똑한 포인터이다. 스마트 포인터는 자기 스스로 하는 일이 존재하는 포인터이다.

스마트 포인터는 객체이다 포인터의 역할을 하는 객체를 뜻하는 것이다.


※ 프로그램의 개발을 목적으로 라이브러리에서 제공하는 스마트 포인터를 사용할 때 큰 도움이 될 것이다.


#include <iostream>

using namespace std;


class Point 

{

    private:

        int xpos, ypos;


    public:

        Point(int x=0, int y=0)

            :xpos(x), ypos(y)

        {

            cout<<"Point object created!!"<<endl;

        }


        ~Point()

        {

            cout<<"Point object cease to exist!!"<<endl;

        }

        

        void SetPos(int x, int y)

        {

            xpos = x;

            ypos = y;

        }


        friend ostream& operator<<(ostream& os, const Point& pos);

};


ostream& operator<<(ostream& os, const Point& pos)

{

    os<<pos.xpos<<", "<<pos.ypos<<endl;

    return os;

}


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 sptr1(new Point(1, 2));

    SmartPtr sptr2(new Point(2, 3));

    SmartPtr sptr3(new Point(3, 4));

    

    cout<<*sptr1;

    cout<<*sptr2;

    cout<<*sptr3;


    sptr1->SetPos(10, 20);

    sptr2->SetPos(20, 30);

    sptr3->SetPos(30, 40);


    cout<<*sptr1;

    cout<<*sptr2;

    cout<<*sptr3;


    return 0;

}


위의 예제에서 가장 중요한 사실은, Point 객체의 소멸을 위한 delete 연산이 자동으로 이뤄졌다는 사실이다.

그리고 바로 이것이 스마트 포인터의 똑똑함이다.

Posted by scii
:

포인터를 기반으로 하는 연산자 모두를 포인터 연산자라 한다. 그런데 그 중에서도 대표적인 포인터 연산자 둘은 다음과 같다.


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

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


※ 둘 다 피 연산자가 하나인 단항 연산자의 형태로 오버로딩 된다는 특징만 기억하면 된다.




19행: 이 함수는 객체자신의 주소 값을 반환하도록 -> 연산자를 오버로딩 하고 있다. 

-> 연산자를 다른 형태로 오버로딩 하는 것도 가능하지만, 이 연산자의 오버로딩을 허용하는 이유는,

주소 값의 반환이 목적이기 때문에 다른 형태로는 오버로딩 하지 않는 것이 좋다.


38행: 19행의 멤버함수가 반환하는 것은 주소 값이니, ShowData 함수의 호출은 문법적으로 성립하지 않는다.  

ex) num.operator->() ShowData();

때문에 반환되는 주소 값을 대상으로 적절한 연산이 가능하도록 -> 연산자를 하나 더 추가하여 진행해야 한다.


operator-> 함수가 반환하는 것이 주소 값이니, 이를 통한 멤버의 접근을 위해서 -> 연산자를 하나 더 추가시켜서 해석한 것이다.

Posted by scii
:

new 연산자 오버로딩


new, delete 연산자의 오버로딩은 다른 연산자 오버로딩과 많이 다르다!!

이 둘은 분명 연산자이다. 따라서 연산자 오버로딩이 가능하며, 이 둘을 이용해서 클래스 별로 독특한 객체의 생성과정을 정의할 수도 있다. 그런데 이 둘에 대한 연산자 오버로딩은 이전에 보았던 연산자 오버로딩과는 다소 차이가 있다.


기본적으로 제공되는 new 연산자가 하는 일


1. 메모리 공간의 할당

2. 생성자의 호출

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


new 연산자가 진행하는 세 가지 작업 중에서 1번에 해당하는 메모리 공간의 할당만 오버로딩 할 수 있다,


다음과 같이 오버로딩 하도록 이미 약속이 되어있다

void* operator new(size_t size) {    }

반환형은 반드시 void 포인터 형이어야 하고, 매개변수는형은 size_t이어야 한다.


※ 일반적으로 size_t 는 다음과 같이 정의되어 있다.

typedef unsigned int size_t;

그래서 0 이상의 값을 표현할 목적으로 정의된 자료형이다.


※ 크기정보는 바이트 단위로 계산되어 전달된다.




delete 연산자 오버로


delete ptr; 이것의 문장으로 객체의 소멸을 명령하면,

컴파일러는 먼저 ptr이 가리키는 객체의 소멸자를 호출한다. 그리고는 다음의 형태로 정의된 함수에 ptr에 저장된 주소 값을 전달한다.

void operator delete(void* adr){    }


따라서 delete 함수는 다음의 형태로 정의해야 한다. 즉, 소멸자는 오버로딩 된 함수가 호출되기 전에 호출이 되니 오버로딩 된 함수에서는 메모리 공간의 소멸을 책임져야 한다. 


void operator delete(void* ptr)

{

delete []ptr;

}


참고로 사용하는 컴파일러에서 void 포인터 형 대상의 delete 연산을 허용하지 않는다면, 위의 delete문을 다음과 같이 작성하면 된다.

delete []((char*)adr);



19행; 컴파일러에 의해, 필요한 메모리 공간의 크기가 바이트 단위로 계산되어서 인자로 전달되니, 크기가 1바이트인 char 단위로 메모리 공간을 할당해서 반환한다. 


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


아직 객체생성이 완성된 상태가 아닌데, 어떻게 멤버함수의 호출이 가능했을까?

그것은 operator new 함수와 operator delete 함수가 static 으로 선언된 함수이기 때문이다.

비록 멤버함수의 형태로 선언을 해도 이 둘은 static 함수로 간주가 된다. 그래서 객체생성의 과정에서 호출이 가능했던 것이다.






operator new & operator new[]


new 연산자는 다음 두 가지의 형태로 오버로딩이 가능하다.


void* operator new(size_t size){    }

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


두번째 함수는 new 연산자를 이용한 배열할당 시 호출되는 함수이다.

Point* arr  = new Point[3]; 이런 문장을 만나면,

컴파일러는 세 개의 Point객체에 필요한 메모리 공간을 바이트 단위로 계산해서, 이를 인자로 전달하면서 new[] 함수를 호출한다.

즉, 열할당 시 호출되는 함수라는 사실을 제외하고는 operator new 함수와 차이가 없다.


마찬가지로 delete 연산자도 다음 두 가지의 형태로 오버로딩이 가능하다.


void operator delete(void* adr){    }

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


두 번째 함수는 delete 연산자를 이용한 배열소멸 시 호출되는 함수이다.

delete []arr;


58행:  객체로 구성된 배열이기 때문에 모든 객체의 소멸자가 호출된 다음에 36행에 정의된 멤버함수(static 함수)가 호출된다.



Posted by scii
: