OOP는 능숙되지 않은 프로그램머에게 나쁜프로그램을 못하게 하는 것이다 #


OOP는 데이터와 그 데이터를 조작하는 오퍼레이터를 캡슐화 함으로서
은닉화를 통한 데이터 보호와 추상화를 통한 인터페이스 노출, 상속을 통한
코드 재활용으로 견고하고 융통성있는 프로그램을 만들기 위한 좋은 개념입니다.
그 구체화된 도구가 class 입니다.


최소 완전한 class를 만들어라 #

Effective C++에서는" 최소 완전한 class를 만들어라 " 라고 얘기 하고 있습니다.
이 얘기는 첨에 이야기한 나쁜프로그램을 못하게하는 것과 일맥 상통하는 내용입니다.
작은 클래스라도 최소 인터페이스라도 완전한 클래스를 구현하는 내용입니다.

그 내용에는 다음과 같은 것들이 있을것입니다.
  • 생성
  • 소멸
  • 복사및 대입
  • 상속


생성 #

디폴트 생성자는 클래스의 기본적인 동작을 하는 내용을 담은 생성자 입니다.
이것은 기본적인 객체의 생성을 도와주고 동적배열 생성과 같은 행동에도 쉽게 문제를 해결해 줍니다.
class Item
{
public:
   Item();  // 디폴트생성자 - 구현생략 
};
허나 id와 같은 값이 필수인 객체를 생성할때 디폴트 생성자를 허용한다면
모든 멤버함수에서 id의 유효성을 검사하는 코드가 난무하거나
혹은 없는 id를 참조하는 불상하는 낼수도 있을것입다.

이럴땐 불편함(?)을 감수해서라도 디폴트 생성자를 제거하는것이 낳을 것입니다.
class Person
{
public:
   Person(int nID) : nID_(nID) {}  // 디폴트 생성자 없음 
private:
   int nID_;
};
그리고 한개의 인수를 가지는 생성자는 암시적인 형변환이 되므로
만약 암시적인 형변환때문에 문제의 소지가 발생할 여지가 있다면
다음과같이 explicit 키워드를 이용해 암시적인 형변환을 막는 것이 좋습니다.
class Person
{
public:
   explicit Person(int nID) : nID_(nID) {}
private:
   int nID_;
};

Person p;
p = 78; // explicit은 이 행동을 막아줍니다. 

만약 Factory Method 같은 패턴을 사용하기위해
정상적인 객체의 생성을 막을려면 디폴트 생성자를
private이나 protected로에 위치시키므로서 해결할수 있습니다.
class NoCreate
{

protected:
        NoCreate() {}
};


소멸 #

생성자에서 혹은 멤버함수에서 생성한 리소스는
항상 소멸자에서 가비지 콜렉션을 해주어야합니다.


복사및 대입 #

복사와 대입은 일관된 정책을 사용합니다
복사생성자가 존재한다면 대입연산자도 만들고
복사생성자가 존재하지 않는다면 대입연산자도 만들지 않습니다.

이유는 객체의 일관성과 일반화된 인터페이스를 제공해 주기 위함입니다.
class Item
{
public:
        Item(const Item& t);        // 복사생성자 
        Item& operator(const Item& t); // 대입연산자 
};

Item::Item(const Item& t)
{
}

Item& Item::operator(const Item& t)
{
        // 재귀치환을 검사합니다. 
        // 재앙을 막아줍니다. 
        if (this == &t) return *this;

        reutrn this;
}

만약 복사와 대입이 필요없다면 다음과같이
객체의 복사도 대입을 막아줍니다.

class NoCopy
{
public:
        NoCopy() {}

private:
        // 두가지 수단으로 함수호출을 저지합니다. 
        // 첫번째는 private 로 선언하여 외부의 노출을 막습니다. 
        // - 컴파일러가 사용을 막아줍니다. 
        // 두번째는 구현을 하지 않습니다. - 링커가 사용을 막아줍니다. 
        NoCopy(const NoCopy& t);        // 복사생성자 
        NoCopy operator(const NoCopy& t); // 대입연산자 
};



상속 #

만약 기초클래스의 용도로 사용될것이라면 소멸자에
virtual 키워드를 추가 하십시요
class Interface
{
public:
        virtual void MemberFunction() = 0;        // 순수가상함수 
        virtual ~Interface() {}
};

기초클래스의 포인터나 참조로 상속된 객체를 핸들링 할때
상속된 클래스의 소멸자가 호출 안되는 재앙을 막을수 있습니다.

Effective C++에 의하면 상속을 막는다면 문서로서 표시하지 말고
문법적으로 상속을 맞는 방법을 택하라고 되어 있습니다.

class Base
{
private:
        Base() {}

public:
        ~Base() {}
};


class Drived : public Base        // private 생성자가 상속을 막아줍니다. 
{};

void main()
{
        Drived d;                // (x) 에러 입니다. 

}


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

Stroustrup - The real interview  (0) 2008.05.01
qsort vs sort  (0) 2008.05.01
RAII (Resource Acquisition Is Initialization)  (1) 2008.05.01
상속되지 않는것  (0) 2008.05.01
STL  (0) 2008.05.01

Resource Acquisition Is Initialization #

직역하면 자원의 획득은 초기화이다 ..

이말은 C++계의 대부 Bjarne Stroustrup 에 의해 나온 관용구 입니다.
http://www.research.att.com/~bs/homepage.html

자원(여기서 자원이란 메모리 뿐만 아니라 OS에 요청에서 얻어지는 모든건 ..)의
소멸(해제)은 Destructor(소멸자)에서 처리 하라는 말과 일맥 상통하는 말입니다.

자원의 획득은 생성자에서, 그 자원의 소멸은 소멸자에서 처리하여 어떠한 경우에도
자원의 leak이 발생하지 않게 처리하자 란 생각입니다.

만약 다음과 같은 코드가 쓰레드의 함수안에서 실행 되고 있을때 ...
CRITICAL_SECTION g_cs;
UINT Run(LPVOID)
{
  if (조건) {
    EnterCriticalSection(&g_cs); // 임계영역에 들어갑니다. 
    AFunction();
    BFunction();
    LeaveCriticalSection(&g_cs); // 임계영역에서 나옵니다. 
  }
  else {
    // 다른 작업 
  }
  return 0;
}
AFunction 함수에서 예외가 발생하여 Run함수를 빠져 나간다면 g_cs의 내용은
임계영역에서 못빠져 나온채 남아 있게 된니다.
그럼 나머지 쓰레드에서 실행되던 코드들은 EnterCriticalSection(&g_cs);
코드 앞에서 모두 대기하는 상태가 되어 프로그램이 DeadLock 상태에 빠지게 됩니다.

그러나 아래의 코드는 ...
class CriticalSection
{
public:
  CriticalSection(CRITICAL_SECTION *pcs) : pcs_(pcs)
  {
    EnterCriticalSection(pcs_);
  }

  ~CriticalSection()
  {
    LeaveCriticalSection(pcs_);
  };

private:
  CRITICAL_SECTION *pcs_;
};

CRITICAL_SECTION g_cs;
UINT Run(LPVOID)
{
  if (조건) {
    // 임계영역에 들어갑니다. 
        // 그리고 cs의 변수 생명이 끝날때 (if 문이 끝날때) 
        // 소멸자가 호출되 임계영역의 자원을 해제 해줍니다. 
    CriticalSection cs(&g_cs);

    AFunction();
    bool b = BFunction();
        if (!b) {
          return -1;
        }
  }
  else {
    다른 작업
  }
  return 0;
}

AFunction혹은 BFunction에서 예외가 발생하던 중간의 return문을
만나서 쓰레드 함수를 탈출한다고 해도 CriticalSection의 소멸자에서
임계영역의 자원을 풀어주므로 보다 안전한 코드를 만들수가 있습니다.

이개념이 RAII(Resource Acquisition Is Initialization) 입니다.

이 RAII 철학을 바탕으로 만들어진 C++ 표준 객체가 std::auto_ptr 입니다.
std::auto_ptr은 생성자에서 할당된 포인터를 받아 소멸자에서
delete 시키는 기능을 가진 포인터타입의 래퍼 객체 입니다.

std::auto_ptr은 다소 익숙하지 않은(일반포인터와 비교해)
소유정책을 가지고 있지만 호환성있는 표준 객체라는 점에서
눈여겨볼 아니 필요할때 꼭 사용되어져야 될 객체 입니다.

#include <iostream>
#include <memory>

void f()
{
  // string 객체를 동적으로 생성하고 명시적인 해제가 없지만 
  // std::auto_ptr의 소멸자에서 string 객체에 대한 할당된 포인터 객체를 
  // delete 해줄 것입니다. 
  std::auto_ptr<string> p(new string("메롱"));

  std::cout << *p << std::endl; // 포인터와 같은 인터페이스를 가집니다. 
  std::cout << p->lenght() << std::endl;
}

int main()
{
        f();
}


이밖에 RAII의 기본철학을 이용한 COW(copy on write)기법이나
Reference Counting 기법을 이용한 smart포인터등 보다 안전하고
융통성있는 객체의 코드를 만들수 있을 것입니다.


MS의 COM은 Reference Counting을 이용한 스마트 포인터로 핸들링 되며
_bstr_t 객체는 COW 타입을 사용해 성능 향상을 꾀한 예로 들수 있을 것입니다.

이밖에도 boost(표준라이브러리는 아니지만 앞으로 표준화의 가능성이 높은 성능좋은
라이브러리)의 smart_ptr 객체들도 auto_ptr의 확장 개념으로서 제공되고 있습니다.


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

qsort vs sort  (0) 2008.05.01
최소 완전한 클래스를 만들어라  (0) 2008.05.01
상속되지 않는것  (0) 2008.05.01
STL  (0) 2008.05.01
new 2  (0) 2008.05.01

상속되지 않는것 #
 
 constructor 
 destructor 
 assign operation 
 static member 
 friend 

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

최소 완전한 클래스를 만들어라  (0) 2008.05.01
RAII (Resource Acquisition Is Initialization)  (1) 2008.05.01
STL  (0) 2008.05.01
new 2  (0) 2008.05.01
new 1  (0) 2008.05.01

STL #

Standard Template Library

STL은 Alexander Stepanov가 Meng Lee와 함께 template으로
구현한 라이브러리 입니다.

1993년 표준화가 이루어질쯤 Alexander Stepanov의 STL을 보고
C++ 표준 위원회의 만장 일치에 의해서 표준에 합류 하게 되었고
STL로 인해 표준화는 몇년 늦어진 1998년에야 이루어 지게 됬습니다.
(이 기간 언어적인 확장은 없었으며 explicit 같은 제한적인 사항이
몇개 추가 되었습니다.)


STL은 공식적인 용어라기 보다 관용적으로 쓰는 C++의
라이브러리 입니다.

STL의 범위는 C++의 표준 라이브러리중 알고리즘과 Iterator를
사용하는 컨테이너 객체를 일반적으로 말합니다.


STL의 의의중 하나는 C++가 OOP언어만이 아닌
multi-paradigm programming language로서의 generic한 기능을
충분히 살린 라이브러리 한 점입니다.


generic programming이란 특정 데이터를 어떻게 가공하느냐의
OOP의 개념 촛점을 맞춘것이 아닌 알고리즘(구현) 부분에
촛점을 맞춘 프로그램밍 방법입니다.

#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <fstream>
using namespace std;

template <class IterT, class FuncT>
void ForEach(IterT B_, IterT E_, FuncT Fun_)
{
        while (B_ != E_) {
                Fun_(*B_++);
        }
}

void Print(char c)
{
        cout << c;
}


int main()
{
        char sz[] = "CHAR ARRAY";
        int nLen = strlen(sz);

        vector<char> v(sz, sz + nLen);

        ForEach(sz, sz + nLen, Print);
        cout << endl;

        ForEach(v.begin(), v.end(), Print);
        cout << endl;


        ifstream fin(__FILE__);
        ForEach(istreambuf_iterator<char>(fin),
                istreambuf_iterator<char>(),
                Print
        );

        cin.get();
}

ForEach는 std::for_each 함수를 흉내 내본 것입니다.

ForEach함수는 특정한 타입에 대해 구현을 해놓은것이 아니라
일반(generic)적인 타입에 대해서 순차적으로 노드를 이동해가며
Fun함수를 호출하는것입니다.

여기서 IterT는 char* 타입이든 특정 컨테이너의 Iterator 타입이든
후치증가연산자 "++" 와 operator*()를 제공되는 어떤 타입이라도
매치가 될수 있으며 FuncT 타입도 T(operator*()가 리턴하는 타입)
타입에 매칭되는 한개의 파라미터를 인수로 취하는 어떠한 함수도
매치가 될수 있습니다.


타입에 종속적이지 않는 일반적인 구현을 제공할수 있다는점이
generic programming의 핵심이며 그 generic programming 방식으로
만들어진 C++의 표준 라이브러리가 STL입니다.



STL의 구성 #


컨테이너 객체로는 아래와 같이 존재하며 algorithm 에는
70여개의 알고리즘 함수가 있고 그외 iterator(반복자)
그리고 유틸리티 함수로 구성 되어 있습니다.

컨테이너 
        deque 
        list 
        map 
        multimap 
        multiset 
        set 
        vector 

알고리즘 함수 
        adjacent_find               
        binary_search               
        copy 
        copy_backward               
        count                       
        count_if                     
        equal                       
        equal_range                 
        fill                         
        fill_n                       
        find                         
        find_end                     
        find_first_of               
        find_if                     
        for_each                     
        generate                     
        generate_n                   
        includes                     
        inplace_merge               
        iter_swap                   
        lexicographical_compare     
        lower_bound                 
        make_heap                   
        max                         
        max_element                 
        merge                       
        min                         
        min_element                 
        mismatch                     
        next_permutation             
        nth_element                 
        partial_sort                 
        partial_sort_copy           
        partition                   
        pop_heap                     
        prev_permutation             
        push_heap                   
        random_shuffle               
        remove                       
        remove_copy                 
        remove_copy_if               
        remove_if                   
        replace                     
        replace_copy                 
        replace_copy_if             
        replace_if                   
        reverse                     
        reverse_copy                 
        rotate                       
        rotate_copy                 
        search                       
        search_n                     
        set_difference               
        set_intersection             
        set_symmetric_difference     
        set_union                   
        sort                         
        sort_heap                   
        stable_partition             
        stable_sort                 
        swap                         
        swap_ranges                 
        transform                   
        unique                       
        unique_copy                 
        upper_bound                 


C++ Standard Library #

C++ Standard Library는 STL을 포함하며 그외에
iostream, complex, string 등등이 template 으로
작성 되어 있습니다.

표준 전의 C++ Standard Library는 모두 일반 클래스로
작성 되었으나 표준화 이후 모든 C++ Standard Library는
template으로 재작성 되었습니다.


그리고 새로운 헤더 형식이 채택 되었습니다.
// 예전 C++ 헤더 방식 입니다. 
// 일반 클래스로 작성된 라이브러리 이며 
// 더이상 표준은 아닙니다. 
// .h 가 붙은 헤더를 사용하시면 ISO C++의 호환성을 
// 보장할수 없습니다. 
// 일부 컴파일러는 옜날 코드를 위해 남겨 두었으나 
// 사용을 권장하지 않습니다. 
#include <iostream.h>



// 표준입니다. 
// template 으로 작성 되었으며 
// std 란 namespace 로 쌓여 있습니다. 
#include <iostream>


규칙은 이렇습니다.
C++ 표준 라이브러리는 .h 가 붙지 않습니다.
그리고 std 란 namespace 에 포함되어 있습니다.


C Standard Library #

C 언어는 C++의 부분 집합이며 라이브러리도 해당됩니다.
C 언어의 표준 라이브러리는 C++의 표준 라이브러리이기도 하며
C 언어의 헤더 형식을 사용 하면 됩니다.

#include <stdio.h>

그러나 C++ 의 header 규칙에 의하여 모든 C 라이브러리들은
앞에 c 가 붙고 .h 가 붙지않는 형식으로도 제공되며 권장되어 지고
그 형식으로 제공된 라이브러리는 std namespace에 포함 되어
있습니다.
#include <cstdio>   // #include <stdio.h> 
#include <cstring>  // #include <string.h> 
#include <cstdlib>  // #include <stdlib.h> 
#include <cmath>    // #include <math.h> 


C++ Headers #

<algorithm> <bitset> <cassert> <cctype> <cerrno> <cfloat> 
<ciso646> <climits> <clocale> <cmath> <complex> <csetjmp> 
<csignal> <cstdarg> <cstddef> <cstdio> <cstdlib> <cstring> 
<ctime> <cwchar> <cwctype> <deque> <exception> <fstream> 
<functional> <iomanip> <ios> <iosfwd> 
<iostream> <istream> <iterator> <limits> <list> <locale> 
<map> <memory> <new> <numeric> <ostream> <queue> 
<set> <sstream> <stack> <stdexcept> <streambuf> <string> 
<strstream> <utility> <valarray> <vector> <cstddef> <cstdlib> 
<exception> <limits> <new> <cstdarg> 

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

RAII (Resource Acquisition Is Initialization)  (1) 2008.05.01
상속되지 않는것  (0) 2008.05.01
new 2  (0) 2008.05.01
new 1  (0) 2008.05.01
initialization - array structure  (0) 2008.05.01

new의 실패 #

new 키워드의 메모리 할당은 operator new 함수에 의해 처리가 됩니다.

이 operator new 함수의 실패는 ISO C++표준 이전에는 NULL을 리턴하게 되었으나 ISO C++표준에서는 std::bad_alloc 객체를 예외로 발생 시키는 것으로 결정 되었습니다.

#include <stdexcept>
#include <iostream>
using namespace std;

int main()
{
        try {
                // 실패한다면 std::bad_alloc 예외 발생 
                int *p = new int[10000000];

                // 구식 컴파일러라면 이 조건에 .. 
                if (!p) {
                        cout << "bad alloc error : old version " << endl;
                        return -1;
                }
        }
       catch (std::bad_alloc &e) {
                cout << "bad alloc error " << endl;
        }
        catch (...) {
                cout << "unknown error " << endl;
        }
}
표준은 준수하는 컴파일러라면 exception(catch (std::bad_alloc &e))이 발생 할것이고 아니라면 if (!p) 조건에 해당되어 처리가 될것입니다.




예외없는 new #

위에서 언급했듯이 표준 이전의 스펙에서만 실패했을때 예외를 발생 시키지 않고 NULL을 리턴 하게 됩니다.

그러나 상황에 따라서는 실패시 NULL을 리턴하는 동작이 필요 할수도 있습니다.
혹은 예외의 비용이 부담되서, 혹은 예외가 지원되지 않아서 등등..

그래서 ISO C++ 표준에서는 예외를 발생시키지 않는 operator new 함수를 추가 했습니다.
void* operator new(std::size_t size, const std::nothrow_t&) throw();
void* operator new[](std::size_t size, const std::nothrow_t&) throw();
일반 operator new 함수에 2번째 인자 const std::nothrow_t& 가 추가가 된 형태 입니다.

#include <new>
#include <stdexcept>
#include <iostream>
using namespace std;

int main()
{
        try {
                int *p = new(nothrow) int[10000000];
                if (!p) {
                        cout << "bad alloc error : old version " << endl;
                        return -1;
                }
        }
        catch (std::bad_alloc &e) {
                cout << "bad alloc error " << endl;
        }
        catch (...) {
                cout << "unknown error " << endl;
        }
}
위의 코드에서는 예외를 발생 시키지 않고 실패시 NULL을 리턴하게 될것입니다.

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

상속되지 않는것  (0) 2008.05.01
STL  (0) 2008.05.01
new 1  (0) 2008.05.01
initialization - array structure  (0) 2008.05.01
operator overloading 2  (0) 2008.05.01

new #

new 키워드는 C++에서 힙영역에 메모리를 할당하기 위해 사용됩니다.

힙영역이란 프로그램(프로세스)이 메모리에 로딩이 될때 사용되는
메모리영역(정적영역, 스택, 코드영역) 을 제외하고 OS 차원에서
허용해주는 메모리 영역입니다.

그래서 힙영역은 프로그램차원에서 다룰수 있는 영역이 아니므로
OS에게 요청을해서 메모리를 받아와야 됩니다.

C언어의 malloc 함수와 C++의 new는 OS에게 그런 요청을 해서
메모리는 얻어오는 함수및 키워드 입니다.


new는 클래스 객체의 생성자를 호출해준다는 점에서
malloc 함수와는 차별화가 됩니다.

#include <iostream>
using namespace std;

class Item
{
public:
        Item() { cout << "ctor" << endl; }
        virtual ~Item() { cout << "dtor" << endl; }
};

int main()
{
        // Item 클래스의 메모리를 확보한후 
        // 생성자를 호출해 줍니다. 
        Item *p = new Item;
        Item *arr = new Item[10];

        // Item 클래스의 소멸자를 호출한후 
        // 메모리를 해제 합니다. 
        delete p;
        delete []arr; // new의 배열은 delete[] 로 해제합니다. 


        // Item 클래스의 메모리만 확보합니다. 
        // 생성자는 호출되지 않습니다. 
        Item *p2 = (Item *p)malloc(sizeof(Item));

        // Item 클래스의 메모리만 해제합니다. 
        // 소멸자는 호출되지 않습니다. 
        // 그것이 new로 생성된 객체일지라고 ... 
        free(p2);
}



operator new #


new의 기능은 크게 두가지로 나눕니다.

첫번째는 메모리를 항당하는 기능이고
두번째는 생성자를 호출하는 기능입니다.

이중 첫번째부분은 사용자에 의해 제어가 가능한 부분이지만
두번째 생성자를 호출하는 부분은 소멸자와 틀리게
컴파일러의 영역일뿐 사용자에 의해서 제어가 불가능한 부분입니다.

그럼 new의 메모리 할당은 어떻게 제어할까?
답은 operator new() 함수를 오버로딩 하는것입니다.


new의 메모리 할당은 내부적으로 컴파일러에 의해 수행 되는것이 아니고
메모리를 할당하는 operator new() 함수를 호출하여 메모리를 할당합니다.
그리고 그것에 대한 결과(할당된 메모리)에 해당 객체의 코드영역을 위치 시킨후
생성자를 호출합니다.

한마디로 컴파일러는 new의 동작에서 메모리 할당부분은
operator new 함수에게 위임 하게 되는것입니다.

그러므로 operator new를 오버로딩 한다면 메모리를 할당받는 과정을
사용자에 의해 변경 할수 있는 것입니다.


#include <iostream>
#include <string>
using namespace std;

class Custom
{
public:
        // 사용자정의 operator new 함수 
        void* operator new (size_t n)
        {
                return malloc(n);
        }

        // operator new를 정의 했다면 
        // 반드시 그에 상응하는 operator delete를 
        // 만들어 재앙을 막읍시다 
        void operator delete(void *p)
        {
                free(p);
        }

};

int main()
{
        Custom *p = new Custom();
        delete p;

}

위의 Custom 객체에 대한 new와 delete는
Custom::operator new() 와 Custom::operator delete()를
사용하게 됩니다.

placement new #


위에서 말했듯이 new의 생성자 호출은 사용자에 의해
제어가 불가능 합니다.
class Creator
{
public:
        Creator() {}
};

int main()
{
        Creator c;

        // 이걸 명시적으로 하고 싶은데 불행히도 
        // 이런것은 하지 못합니다. 
        // 에러 !! 
        c.Creator();        // 생성자호출 
}

그러나 또다른 new의 버전 placement new를 이용하면
비슷한 효과를 낼수도 있습니다.


placement new의 기본적인 내용은 메모리 할당은 직접 안하고
할당된 메모리에 객체를 생성 시키는 일만 합니다.

#include <iostream>
#include <string>
using namespace std;


int main()
{
        // 메모리 할당만 합니다. 
        // 생성자 호출은 안합니다. 
        string *pHeap = (string*)malloc(sizeof(string));
        // C++식 단순 메모리 할당 입니다. 
        // malloc과 같은 내용입니다.   
        // string *pHeap = (string*)operator new (sizeof(string)); 


        // 정적으로 메모리를 잡습니다. 
        // 당연히 스택에 메모리가 잡히겠죠   
        char pStack[sizeof(string)];

        // 메모리 생성은 안하고 
        // 생성된 메모리 pBuff에 string을 코드를 
        // 올려 놓고 생성자를 호출합니다. 
        new (pHeap) string("할당된메모리에 생성");

        // 스택에 string 객체를 생성합니다. 
        new (pStack) string("정적메모리에 생성");


        // 소멸자를 명시적으로 호출해 줍니다. 
        // 미리 할당된(정적이든 동적이든) 메모리에 
        // 객체만 생성하는것이기 때문에 
        // pHeap이 메모리가 해제 된다고 해도 string의 
        // 소멸자가 자동으로 호출되지 않습니다. 
        // 그러므로 소멸자를 호출 안하거나 
        // 두번이상 소멸자를 호출하는 재앙은 
        // 사용자의 책임입니다.   
        pHeap->~string();

        // 소멸자 호출   
        // pStack은 char타입의 배열이므로 
        // string* 형으로 명시적인 형변화 필요   
        reinterpret_cast<string *>(pStack)->~string();

        // pHeap의 메모리를 해제 합니다. 
        // operator new로 할당 했다면 
        // operator delete(pHeap) 
        free(pHeap);

        // pStack은 stack에 할당 되었으므로 
        // 해제가 필요없습니다. 
        // 이 함수가 끝나면 자동으로 스택에서 
        // 제거 됩니다.   

}

placement new는 메모리 할당의 제약이 있는 임베디드시스템이나
혹은 다른 프로그램 기법상 필요한
(예를들어 기본생성자가 없는 객체의 배열생성이나 메모리 풀링 등등)
일을 할때에 유용하게 사용할수 있을 것입니다.


참고: More Effective C++

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

STL  (0) 2008.05.01
new 2  (0) 2008.05.01
initialization - array structure  (0) 2008.05.01
operator overloading 2  (0) 2008.05.01
operator overloading 1  (0) 2008.05.01

배열및 구조체의 초기화 #

// 3개의 원소를 각각 1, 2, 3 으로 초기화 
int nArr[3] = { 1, 2, 3 };

// 4개의 원소를 가지는 배열 4개를 1, 2, 3, 4 로 초기화 
// 이때 배열의 개수는 4개로 자동으로 정의됨 
int nArr[] = { 1, 2, 3, 4 };

struct St {
  int a;
  int b;
};

// 구조체 St의 멤머 a, b를 각각 1, 2로 초기화 
St st = { 1, 2 };

// 구조체의 배열 초기화 
St st[2] = { { 0, 1 }, { 2, 3 } };


초기화 되지 않은 auto 변수는 정의되지 않은 값을 가진다 #

// 어떤값이 들어있는지 정의되지 않았음 
int nArr[3];
St st;
St st[2] ;

// 물론 배열및 구조체 변수가 아닌 일반 auto 변수도 같이 적용 
int n; // 정의되지 않음 


부분초기화된 배열및 구조체 #

부분 초기화된 배열및 구조체의 나머지 원소나 멤버의 값은 static변수처럼(0으로 초기화) 된다

// 첫번째 원소에 0을 초기화 하고 나머지 2개의 원소에는 0을 적용 
int nArr[3] = { 0 };

// 첫번째와 두번째에 각각 1, 3으로 값을 초기화 하고 나머지 3개의 원소에 대해서는 0으로 초기화 한다 
int nArr[5] = { 1, 3, };

// 맨 마지막 콤마는 생략 가능 
int nArr[5] = { 1, 3 };


struct St {
  int a;
  int b;
};

// St의 a의 멤버에 2를 초기화 하고 나머지멤버인 b에 0을 초기화 
St st = { 2 };
St st = { 2, };


모든원소에 0으로 초기화 #

struct St {
  int a;
  int b;
};

// 0으로 초기화 하는 같은 내용들   
St st = { 0, 0 };
St st = { 0, };
St st = { 0 };
St st = {};

int nArr[5] = { 0, 0, 0, 0, 0 };
int nArr[5] = { 0, };
int nArr[5] = { 0 };
int nArr[5] = {};

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

new 2  (0) 2008.05.01
new 1  (0) 2008.05.01
operator overloading 2  (0) 2008.05.01
operator overloading 1  (0) 2008.05.01
Conversion Functions - 변환함수  (0) 2008.05.01

operator->() #

"->"의 연산자 겹지정은 포인터를 리턴으로 하는 형식을 취하고 있으며
리턴되는 포인터의 "->" 연산자를 사용한 효과를 내는 특이한 특성을 가지고 있습니다.

이러한 형식으로 operator->() 함수의 리턴되는 포인터의 멤버를 제어할 수 있는 능력을 부여합니다.


#include <iostream>
#include <string>
using namespace std;

template <class T>
class SmartPtr
{
public:
        explicit SmartPtr(T *p = 0) : p_(p) {}
        virtual ~SmartPtr() { delete p_; }

        T* operator->() {
                return p_;
        }

        // 아래의 두개의 함수인 "!" 연산자 겹지정 함수와, 
           // bool 타입으로의 변환연산자 함수는 융통성있는 인터페이스로 인하여   
        // 문제가 생길 여지가 있으므로 사전에 그것에 대해 알고 사용 하셔야 
        // 미래의 재앙에서 벗어날수 있습니다. ^^ 
        bool operator!() {
                return p_ == null;
        }

        operator bool() {
                return p_ != NULL;
        }

private:
        // 복사생성자와 대입연산자를 사용 못하게 하기위해 
        // private 멤버로 두었으며 구현을 하지 않았습니다. 
        SmartPtr(const SmartPtr& sp);
        SmartPtr& operator=(const SmartPtr& sp);

private:
        T *p_;
};


int main()
{
        SmartPtr<string> sp(new string("ABCDEFG"));

        // T* operator->() 함수가 호출됩니다. 
        // 내부 string 객체의 length() 함수를 호출합니다. 
        cout << sp->length() << endl;

}

위는 간단한 Smart Pointer 예제입니다.

위의 "->" 연산자의 연잔자 겹지정 함수를 보면 T* 타입,
즉 main 함수의 코드 대로 라면 string* (stirng 타입의 template 이므로) 타입을 리턴하게 됩니다.

그럼 "->"의 겹지정함수는 다시 string* 타입에 대해 "->" 적용하여
string의 멤버에 접근 가능하게 만들어 줍니다.

만약 operator->()의 겹지정 합수에 리턴되는 포인터의 멤버를 제어할수 있는 능력이 없다면
아마 다음과 같이 코드를 작성해야 될것입니다.
cout << sp->->length() << endl;         // 혹은 
cout << (sp->)->length() << endl;   // 혹은 
cout << (*(sp->)).length() << endl;
하지만 위의 같은 코드는 컴파일 되지도 않을 뿐더라 인터페이스가 직관 적이지 못합니다.
그러한 이유료 C++에서는 operator->() 함수 즉 "->" 연산자 겹지정 함수에 대해 그러한
능력이 부여된 것입니다.


이러한 스마트 포인터는 C++의 표준 객체인 std::auto_ptr 부터 boost::shared_ptr,
ATL에서의 _com_ptr 객체 까지 두루두루 사용되고 있으며, 내부멤버를 접근을 제어를 위한
Proxy Pattern 같은 곳에서 유용하게 사용될 수 있는 기능입니다.




">>", "<<" 연산자 겹지정 #

">>" 혹은 "<<" 연산자는 비트단위의 Shift 연산을 하는 기능을 가지고 있는 연산자 입니다.
당연히 ">>" 혹은 "<<" 연산자도 사용자 정의 타입인 클래스 객체에 대해 Shift 연산을 수행하는
코드를 생성할수도 있고, 또한 다른 기능을 사용자 정의에 의해 구현될 수 있습니다.

그리고 또 하나의 의미인 스트림 객체의 in, out 기능에 해당하는 인터페이스를 매치 시킬수도 있습니다.

C++의 입문서중 가장 처음에 등장하는 코드인 Hello World 코드 입니다.

#include <iostream>
using namespace std;

int main()
{
        cout << "Hello World" << endl;
}
이 예제는 바로 cout 객체의 연산자 겹지정 함수인 operator<<()에 의해 가능해진 코드 입니다.

그럼 간단하게 C++의 "<<" 연산자 사용을 통하여 C++의 스트림 객체와의 융통성있는 인터페이스를
간단히 구현해 보자면 ...


#include <iostream>
#include <string>
#include <fstream>
using namespace std;


class Student
{
public:
        /* ... */

        string strName_;
        int nID_;
};


ostream& operator<<(ostream& os, const Student &st)
{
        os << st.strName_ << " " << st.nID_;
        return os;
}


int main()
{
        Student st;

        /* ... */

        // 파일스트림으로 st의 내용을 보냅니다. 
        ofstream fout("a.txt");
        fout << st;

        // 표준 출력 스트림으로 st의 내용을 보냅니다. 
        cout << st;
}
여기서 주의 할점은
ostream& operator<<(ostream& os, const Student &st)
와 같이 참조를 리턴해야 된다는 점입니다.
그래야 다음과 같은 코드가 가능해 집니다.
fout << "VALUE: " << st << " END" << endl;
그리고 위의 겹지정 함수는 Student에 멤버로도 작성이 가능합니다.
하지만 다음과 같은 문제점이 발생합니다.

class Student
{
public:
        /* ... */

        Student& operator<<(ostream& os) {
                os << strName_ << " " << nID_;
                return *this;
        }

        string strName_;
        int nID_;
};

int main()
{
        Student st;
        /* ... */

        ofstream fout("a.txt");

        // 관습적이지 못한 인터페이스 입니다. 
        // 사용하는 사람은 일관성 있는 인터페이스를 찾지못해 
        // 혼란에 빠질지도 모릅니다. 
        st << fout;
        st << cout;
}
그러므로 "<<" 와 같은 이항 연산자의 경우 friend 함수나 전역함수로 작성하는것이 관례입니다.
(표준 라이브러리는 대부분 전역함수로 작성되어 있습니다.)

전역함수로 연산자 겹지정을 구현 할때는 인자중 적어도 한개는 사용자 정의 타입이어야 합니다.
그러므로 다음과 같은 코드는 컴파일 에러 입니다.

// 사용자 정의 타입이 없습니다. oops~ 
double operator+(int n, double d)
{
        return n + d;
}

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

new 1  (0) 2008.05.01
initialization - array structure  (0) 2008.05.01
operator overloading 1  (0) 2008.05.01
Conversion Functions - 변환함수  (0) 2008.05.01
explicit  (0) 2008.05.01

operator overloading #

연산자 겹지정은 c++의 사용자 정의 타입 - class - 에 기본타입의 인터페이스를
부여할때 사용합니다.

예를들어 집합 객체에 "+" 연산자를 이용해서 합집합을 구현한다던가
스트림 객체와의 인터페이스를 위해 "<<" 나 ">>" 를 구현하는것이 여기에 속합니다.

연산자 겹지정은 연산자 마다 각각 그 의미에 맞게 구현 할수도록 규칙이 존재합니다.

예를들어 +, -, /, * 연산자는 각각 의미에 맞에 2항 연산자로서 사용이 되야하며
+, - 는 더불어 부호를 표시하는 단항 연산자로서 사용할수 있고
*는 포인터가 가르키는 의미로서의 단항연산자로 사용이 가능합니다.

그럼 이걸 어떻게 다 외우느냐 라고 생각이 드실지 모릅니다.
그때 쉽게 적용하는 방법은 Scott Meyers의 연산자 겹지정에 대한 철학인
"when in doubt, do as the ints do"(의심이 가면 int처럼 해라)의 법칙과 같이
int형에서 연산자 인터페이스처럼 동작을 한다고 보시면 됩니다.

그리고 그렇게 구현을 하는것이 좋은 방법이고요.
+ 연산자의 구현에 값을 차감하던가 나누는 일을 한다면 정말 곤란 하겠지요.


그럼 모든 연산자를 겹지정을 할수 있는가?
대답은 아닙니다. 몇개의 연산자는 겹지정을 할수가 없습니다.



example #

#include <iostream>
using namespace std;


class Set
{
public:
        Set() {}
        Set(const Set &s) {}
        ~Set() {}

public:
        Set& operator=(const Set &s); // 대입연산자 

        Set operator+(const Set &s) const; // 합집합 
        Set operator-(const Set &s) const; // 차집합 
        Set operator*(const Set &s) const; // 교집합 
};

Set& Set::operator=(const Set &s)
{
        if (this != &s) {
                // 대입을 합니다. 
        }
        return *this;
}

Set Set::operator+(const Set &s) const
{
        Set r;
        // 합집합 구현 ... 
        return r;
}

Set Set::operator-(const Set &s) const
{
        Set r;
        // 차집합 구현 ... 
        return r;
}

Set Set::operator*(const Set &s) const
{
        Set r;
        // 교집합 구현 ... 
        return r;
}


int main()
{
        Set a;
        Set b;

        // 합집합을 구합니다. 
        // Set c = a.operator+(b); 
        Set c = a + b;

        // 차집합을 구합니다. 
        // Set c = a.operator-(b); 
        Set d = a - b;

        // 교집합을 구합니다. 
        // Set c = a.operator*(b); 
        Set e = a * b;

        // 대입연산자를 이용합니다.   
        // a.operator=(b); 
        a = b;

}

위의 예제는 +, -, *, = 연산자를 이용해 집합의 연산을 하는 예제입니다.
Add(), Sub(), Mul(), Assign() 등의 멤버 변수를 만들어서 처리 할수도 있지만
연산자를 이용함으로서 보다 유연한 코드가 만들어 졌습니다.



#include <iostream>
using namespace std;

class CInt
{
public:
        CInt() {}
        ~CInt() {}

public:
        CInt operator+(int n) const;

private:
        int _n;
};

CInt CInt::operator+(int n) const
{
        CInt ci;
        // ci와 n을 더합니다. 
        return ci;
}

int main()
{
        CInt ci;

        // CInt r = ci.operator(45); 
        CInt r = ci + 45;

        // (x) 컴파일 에러 
        // CInt r2 = 45.operator(ci); 
        CInt r2 = 45 + ci;
}

위의 코드는 CInt타입이 정수와의 + 연산을 겹지정 한 예제입니다.

그러나 CInt r2 = 45 + ci; 를 실행하는 순간 CInt r2 = 45.operator(ci); 과 같은
문법적 오류가 발생하고 맙니다.


CInt r2 = 45 + ci; 코드를 실행하기 위해 45숫자에대해 연산자 겹지정을 할수는 없습니다.
멤버 함수로서 operator+() 함수는 좌항에 객체 자신의 취하고(*this) 우항에는 파라미터를 취하게 됩니다.
그렇기 때문에 45는 객체가 될수 없기때문에 위와 같은 코드는 실행할수는 없습니다.

이 코드를 실행 하기 위해서는 멤버함수가 아닌 전역함수 혹은 friend 함수로 작성하면 가능합니다.

전역함수나 friend함수로 작성하게되면 operator+() 함수는 두개의 파라미터(좌항, 우항)를 취하게 됩니다.



다음은 friend함수를 사용하는 예제입니다.

#include <iostream>
using namespace std;

class CInt
{
public:
        CInt() {}
        ~CInt() {}

public:
        CInt operator+(int n) const;

        // friend함수로 작성합니다. 
        friend CInt operator+(int n, const CInt &ci);
private:
        int _n;
};

CInt CInt::operator+(int n) const
{
        CInt ci;
        // ci와 n을 더합니다. 
        return ci;
}

CInt operator+(int n, const CInt &ci)
{
        // ci.operator(n); 을 이용합니다.   
        return ci + n;
}


int main()
{
        CInt ci;

        // CInt r = ci.operator(45); 
        CInt r = ci + 45;

        // CInt r2 = operator+(45, ci); 
        CInt r2 = 45 + ci;
}

friend CInt operator+(int n, const CInt &ci); 과 같이 friend로 작성하면
CInt의 private 혹은 proteced 멤버에 대해 접근을 할수 있습니다.

그러나 위의 코드처럼 private 혹은 proteced 멤버에 접근하지 않는다면
아래와 같이 전역함수로 작성하여 객체에 대한 의존성을 줄일수 있습니다.



#include <iostream>
using namespace std;

class CInt
{
public:
        CInt() {}
        ~CInt() {}

public:
        CInt operator+(int n) const;

private:
        int _n;
};

CInt CInt::operator+(int n) const
{
        CInt ci;
        // ci와 n을 더합니다. 
        return ci;
}


// 전역 함수 
CInt operator+(int n, const CInt &ci)
{
        // ci.operator(n); 을 이용합니다.   
        return ci + n;
}


int main()
{
        CInt ci;

        // CInt r = ci.operator(45); 
        CInt r = ci + 45;

        // 전역 함수 사용 
        // CInt r2 = operator+(45, ci); 
        CInt r2 = 45 + ci;
}


증가연산자의 prefix와 postfix를 구분하기 #

#include <iostream>
using namespace std;

class CInt
{
public:
        CInt() {}
        ~CInt() {}

public:
        // prefix : ++CInt 
        CInt& operator++();

        // postfix : CInt++ 
        const CInt operator++(int);

private:
        int n_;
};


CInt& CInt::operator++()
{
        ++n_;
        return *this;
}

const CInt CInt::operator++(int)
{
        CInt ci(*this);
        ++(*this);

        return ci;
}


int main()
{
        CInt ci;

        ci++;
        ++ci;
}

const CInt operator++(int); 함수의 int 파라미터는 int형의 파라미터를 의미하는것이 아니라 postfix의 증가연산자를 표현하기 위해 사용된 것입니다.

그리고 postfix의 const CInt operator++(int); 함수의 리턴형을 자세히 보면 const CInt로 되어 있는 것을 볼수 있습니다.

이 반환값은 아래와 같은 코드를 막을수 있습니다.
CInt ci;
ci++++; // const CInt의 리턴형이 아래의 코드에 에러를 발생시킵니다. 

여기서 또한가지 주목할점은 postfix 증가연산자는 prefix 증가연산자를 이용해 구현한다는 점입니다.
이렇게 하는것이 보다 견고한 코드를 만들수 있기 때문입니다.

그리고 postfix가 비용이 prefix보다 더 비용이 들어가는것을 볼수 있습니다.
되도록이면 prefix를 사용하는것이 성능향상에 도움이 됩니다.

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

initialization - array structure  (0) 2008.05.01
operator overloading 2  (0) 2008.05.01
Conversion Functions - 변환함수  (0) 2008.05.01
explicit  (0) 2008.05.01
template  (0) 2008.05.01

Conversion Functions #

변환함수

객체를 특정한 타입으로 암시적으로 변환할수 있는 능력을
부여하는 함수입니다.

class String
{
public:
        /* ... */

        // 변환함수 
        // 암시적으로 const char* 타입으로 변환하게 합니다. 
        operator const char*()
        {
                return pstr;
        }

private:
        char *pstr;
};


int main()
{
        String str;

        // 변환 함수가 사용됩니다. 
        const char *pstr = str;
}

변환함수는 많은 융통성을 제공하고 프로그램하는데 있어
좋은 인터페이스를 제공합니다.


허나 변환함수는 많은 함정을 가지고 있습니다.
의도적이지 않는 형변환으로 인하여 문제를 야기 할수 있습니다.


#include <iostream>
using namespace std;

class String
{
public:
        String(const String &str)
        {
                pstr_ = new char[strlen(str.pstr_) + 1];
                strcpy(pstr_, str.pstr_);
        }

        explicit String(const char *pstr = NULL)
        {
                pstr_ = new char[strlen(pstr) + 1];
                strcpy(pstr_, pstr);
        }

        ~String()
        {
                delete pstr_;
        }

        String& operator=(const String &str)
        {
                if (this != &str) {
                        if (pstr_) {
                                delete pstr_;
                        }

                        pstr_ = new char[strlen(str.pstr_) + 1];
                        strcpy(pstr_, str.pstr_);
                }

                return *this;
        }

        operator const char*() const
        {
                return pstr_;
        }

private:
        char *pstr_;
};


int main()
{
        String str("AAA");

        // 컴파일 에러가 생겨야 하지만 문제없이 
        // 컴파일 됩니다. 
        // 변환함수때문에 포인터값을들 비교하는 
        // 의도하지 않은 결과가 이루어 집니다. 
        if ("AAA" == str) {
                /* ... */
        }

}
변환함수는 위에 예제처럼 의도하지 않는결과를 만들어 내기
때문에 사용을 신중히 하셔야 합니다.

물론 operator==()를 정의했다면 문제는 달라지지만
여하튼 문제의 소지는 있습니다.


C++표준위원회에서 기획한 std::string 객체도 변환함수를
제공하지 않습니다.

대신 .c_str() 이란 멤버함수를 제공함으로서 C타입의 문자열에대해
변환할수 있는 인터페이스를 제공하고 있습니다.




참조:
More Effective C++

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

operator overloading 2  (0) 2008.05.01
operator overloading 1  (0) 2008.05.01
explicit  (0) 2008.05.01
template  (0) 2008.05.01
functor - 함수객체  (0) 2008.05.01

explicit #

암시적으로 클래스 타입으로의 형변환을 막는 키워드 입니다.

비교적 C++표준의 후반부에 채택된 사항이며 클래스의 생성자에 사용하는
키워드 입니다.

어떤 타입(객체)에서 특정 타입으로의 암시적인 형변환은 파라미터가 1개인
생성자에 근거하여 이루어 집니다.

#include <iostream>
using namespace std;

class CInt
{
public:
        CInt() {}
        CInt(int n) : n_(n) {}
        ~CInt() {}

        operator int()
        {
                return n_;
        }

private:
        int n_;
};


int main()
{
        CInt Number;

        // 파라미터가 1개인 생성자인 
        // CInt(int n)에 근거하여 암시적인 
        // 형변환이 이루어 집니다.   
        Number = 34;


        // operator int()의 변환연산자를 통하여 
        // CInt객체가 int 형으로 암시적으로 형변환이 
        // 가능합니다. 
        int n = Number;
}
위의 예제는 int형의 객체를 나타내는 CInt클래스를 표현 해봤습니다.
CInt 클래스에서는 int 형에서 CInt형으로의 변환이 타당해 보입니다.

허나 다음 예제를 보시면 ...

#include <iostream>
using namespace std;

// String 타입의 배열객체 
class StringVector
{
public:
        // 디폴트생성자   
        StringVector() {}

        // n개의 String형의 배열 객체를 생성합니다. 
        explicit StringVector(int n) {}

        ~StringVector() {}
};


int main()
{
        StringVector vec;

        // 이것은 아무의미가 없습니다. 
        // 컴파일러에 의해 막아줘야 합니다. 
        // 이때 사용하는 것이 
        // explicit 키워드 입니다. 
        // 아래의 문장은 컴파일러에 의해 에러를 보고 할것 입니다. 
        vec = 34;
}

String 타입의 배열객체에 대해 int타입을 대입하는것은 의미가 없습니다.
그러나 파라미터가 한개인 생성자에 의해 암시적으로 형변환이 가능합니다.

이것을 막아주기 위해서는 파라미터가 한개인 생성자에 explicit 제한자를 부여해
암시적인 형변환을 막아줍니다.

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

operator overloading 1  (0) 2008.05.01
Conversion Functions - 변환함수  (0) 2008.05.01
template  (0) 2008.05.01
functor - 함수객체  (0) 2008.05.01
Casting operators  (0) 2008.05.01

generic #

template은 C++의 generic한 기능을 받쳐주는 핵심적인 도구입니다.

이 도구에 의해 탄생한 STL과 표준 라이브러리들은
확장성과 타입 안정성등의 이점으로 다른 언어의 라이브러리들과는
다른 진화된 디자인을 보여주고 있습니다.

이름 그대로 template은 타입에대한 명세서와 같습니다.

template 자체가 타입이 아니고 타입에 대한 내용을 기술함으로서
타입생성에 틀이 되는 것입니다.

붕어빵을 예를들면 붕어빵에 팥을 넣느냐 아니면 계란을 넣느냐에 따라
붕어모양의 붕어빵이 나오느냐 붕어모양의 계란빵이 나오느냐와
같은 이치입니다.


그 붕어빵 틀이 template 입니다.


template #

#include <string>
using std::string;

template <class T>
T Add(T tp1, T tp2)
{
        return tp1 + tp2;
}

int main()
{
        int n = Add(5, 2);
        string str1 = "C";
        string str2 = "++";
        string str = Add(str1, str2);
}

위에 코드에서 Add는 두 가지 타입에 대해 사용되었습니다.
한개는 int형이며 한개는 std::string 형입니다.

컴파러는 위의 코드를 컴파일 할때
template <class T>
T Add(T tp1, T tp2)
{
        return tp1 + tp2;
}
이 코드를 틀로 하여 두개의 함수를 만들어 낼것입니다.

int Add(int tp1, int tp2)
{
        return tp1 + tp2;
}

std::string Add(std::string tp1, std::string tp2)
{
        return tp1 + tp2;
}
실제로 컴파일러의 컴파일작업은 위의 두개의 생성된 함수를 이용하여
컴파일을 합니다.

만약 + 연산자를 제공하지 않는 타입이나 객체를
Add 함수에 인수로 제공을 한다면 컴파일러는 에러 메세지를 출력 할것 입니다.

class NoOperator {};

int main()
{
        NoOperator Op1;
        NoOperator Op2;

        // 에러입니다. 
        // NoOperator는 operator+() 제공하지 않습니다. 
        Add(Op1, Op2);
}
이것이 타입 안정성의 단적인 예를 보여준것 입니다.
적법하지 않는 객체나 타입은 컴파일 타임에 에러를 발생시켜
런타임상의 에러를 막아줄수 있습니다.


클래스도 위의 함수와 같이 적용됩니다.

#include <string>
using std::string;

template <class T>
class TWrapper
{
public:
        T& GetRefValue()
        {
                return tp_;
        }

private:
        T tp_;
};


int main()
{
        TWrapper<int> Int;
        TWrapper<string> String;
}

위의 int와 string 타입의 TWrapper 템플릿은 다음과 같이
코드가 생성 될것입니다.


<int 버전>
class TWrapper
{
public:
        int& GetRefValue()
        {
                return tp_;
        }

private:
        int tp_;
};

<string 버전>
class TWrapper
{
public:
        string& GetRefValue()
        {
                return tp_;
        }

private:
        string tp_;
};
물론 TWrapper에 대한 두개의 같은 이름은 컴파일러에 의해
적법하게 치환될것 입니다.



template specialization #

템플릿 전문화는 template이 특정 타입에 특정한 행동을 할수 있도록
만들어 놓은 것입니다.

template <class T>
T Add(T tp1, T tp2)
{
        return tp1 + tp2;
}

int main()
{
        const char *sz = Add("C", "++");
}
위의 Add는 const char*와 매치가 될것 입니다.

const char* Add(const char* tp1, const char* tp2)
{
        return tp1 + tp2;
}
우선 const char* 가 const 타입이란 점에서 +연산을 수행할수 없으며
어떻해든 강제로라로 +연산을 수행하더라도 char* 타입의 +연산은 아무런
의미가 없는 것이고 원하는 결과도 아닐것 입니다.

이럴때 const char* 에 대해 전용 Add함수를 만들오 주고 싶은 욕망이
생길수도 있을겁니다.

이럴때 사용하는것이 template specialization입니다.

template <class T>
T Add(T tp1, T tp2)
{
        return tp1 + tp2;
}

template <>
const char* Add(const char* p1, const char* p2)
{
        static char sz[1024] = {0, };
        strcpy(sz, p1);
        strcat(sz, p2);
        return sz;
}


int main()
{
        // template Add 함수 버전을 사용합니다. 
        int n = Add(3, 2);

        // const char* 특화버전 Add 함수를 사용합니다. 
        const char* p = Add("C", "++");
}

클래스의 전문화도 같이 적용되며 특정타입을 사용못하도록 하는
버전을 만들어 보겠습니다.

template <class T>
class TWrapper
{
public:
        T& GetRefValue()
        {
                return tp_;
        }

private:
        T tp_;
};


// const char* 타입에 대해서는 인스턴스를 못만들게 하기위해 
// 아래와 같은 템플릿 클래스의 전문화를 만듭니다. 
template <>
class TWrapper<const char*>
{
private:
        TWrapper() {}; // private 생성자입니다. 
};


int main()
{
        // 성공입니다. 
        TWrapper<int> Int;

        // 에러입니다. 
        // 위의 const char* 의 클래스전문화가 
        // const char* 인스턴스생성을 막아줍니다. 
        // 기특하기도 하쥐 -_- 
        TWrapper<const char*> CharP;
}

Members of Class Templates #

클래스(템플릿클래스도 포함)의 멤버함수도 템플릿이 될수 있습니다..

class Adder
{
public:
        template <class T>
        T Add(const T& t1, const T& t2)
        {
                return t1 + t2;
        }
};


int main()
{
        Adder adder;
        int n = adder.Add(3, 4);
        double d = adder.Add(5.3, 2.3);
}

template partial specialization #

특정 타입의 행동에 대해 정의 하는 템플릿전문화와 달리
부분 템플릿 전문화는 템플릿 인수 T에 대해 포인터형이나 참조형과
같은 형의 부분적인 타입의 특성을 전문화를 할때 사용됩니다.

#include <iostream>
#include <string>
using namespace std;


template <class T>
class Any
{
public:
        string GetType()
        {
                return "class Any";
        }
};


template <class T>
class Any<T *>
{
public:
        string GetType()
        {
                return "class Any<T *>";
        }
};

template <class T>
class Any<const T>
{
public:
        string GetType()
        {
                return "class Any<const T>";
        }
};


template <class T>
class Any<const T*>
{
public:
        string GetType()
        {
                return "class Any<const T*>";
        }
};


int main()
{
        Any<int> A1;
        cout << A1.GetType() << endl;

        Any<int *> A2;
        cout << A2.GetType() << endl;

        Any<const int> A4;
        cout << A4.GetType() << endl;

        Any<const int *> A5;
        cout << A5.GetType() << endl;
}

결과:
class Any
class Any<T *>
class Any<const T>
class Any<const T*>

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

Conversion Functions - 변환함수  (0) 2008.05.01
explicit  (0) 2008.05.01
functor - 함수객체  (0) 2008.05.01
Casting operators  (0) 2008.05.01
export  (0) 2008.05.01

+ Recent posts