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

functor #


functor(함수객체)는 함수의 인터페이스를 지원하는 객체를 말합니다.
구체적으로 말하면 class에 operator()를 overloading하여 사용하는
관용구를 말합니다.

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


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

  int operator()(int lvalue, int rvalue)
  {
    return nSum_ = (lvalue + rvalue);
  }

  int operator()(int value)
  {
    return nSum_ += value;
  }

  int Result() const
  {
    return nSum_;
  }

private:
  int nSum_;

};


int FnAdd(int lvalue, int rvalue)
{
  return lvalue + rvalue;
}


int main()
{
  Adder add;

  cout << add(3, 6) << endl;
  cout << FnAdd(2, 4) << endl;

  cout << add(1) << endl;
  cout << add.Result() << endl;
}

위의 코드는 함수와 함수객체와의 단적인 확장성을 볼수 있습니다.

Adder객체는 함수 FnAdd와 인터페이스는 같으면서 두수를 더하고 그 결과를 리턴과
동시에 저장을 할수 있으며 그 결과값으로 다시 연산을 할수 있는
확장성을 보여줍니다.

예에서 볼수 있듯이 함수객체는 객체에 함수인터페이스를 추가함으로서
함수 호출 부분에 대신 들어가 더욱더 확장성 있는 일을 해낼수가 있습니다.


그리고 함수포인터로는 불가능한 inlining 작업을 수행함으로서 성능향상을
꽤할수도 있습니다.


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


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

  template <class T>
  void operator()(const T &ty)
  {
    cout << ty << "!";
  }
};

void Print(int n)
{
  cout << n << ":";
}


template <class IterT, class FunT>
void ForEach(IterT begin, IterT end, FunT fun)
{
  while (begin != end) {
    fun(*begin++);
  }
}


int main()
{
  enum { ARR_SIZE = 5 };
  int nArr[ARR_SIZE] = { 1, 2, 3, 4, 5 };

  Printer printer;
  ForEach(nArr, nArr + ARR_SIZE, printer);
  cout << endl;

  ForEach(nArr, nArr + ARR_SIZE, Print);
  cout << endl;

  cin.get();
}

위의 Printer객체의 인스턴스인 printer는 void operator()(const T &ty) 가
인라인으로 되어 있어 ForEach 함수안에서 함수의 오버헤드 없이 호출이 되지만
Print함수는 앞에 inline키워드를 붙여 인라인을 강제하여도 함수포인터를 호출하려면
함수의 주소가 필요한 규칙에의해 컴파일러에게 인라인이 무시가 됩니다.


물론 인라인의 무절제한 사용으로 인한 성능 저하를 배제한다면 ...

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

explicit  (0) 2008.05.01
template  (0) 2008.05.01
Casting operators  (0) 2008.05.01
export  (0) 2008.05.01
main function  (0) 2008.05.01

Casting operators #

C++에서는 4가지의 cast 연산자가 추가 되었습다.
물론 C언어와의 호환성을 위해 C언어의 문법도 지원합니다.
그러나 명확하고 보다 안전함 때문에 C++의 cast를 추천합니다.


static_cast #

사전적 의미는 런타임시 타입에 대한 체크와 cast가 이루어지는것이 아니고
컴파일시에 체크와 cast되는 연산자

일반적으로 C언어의 형변환이 여기에 속합니다.
그리고 base 클래스에서 derived 클래스로의 down cast시에도 이용되는데
런타임시 타입을 체크하지 않고 cast가 되기 때문에 오버헤드는 없으나
잘못된 포인터로 인한 위험은 존재합니다.
class Base {};
class Derived : public Base {};

void f(Derived *p)
{
}

int main()
{
  Base *p = new Derived;
  f(static_cast<Derived *p>(p)); // down cast 

  int i = 39;
  unsigned long lvalue = static_cast<unsigned long>(i); // 일반적인 cast 
  unsinged long lvalue2 = (unsigned long)i;  // C언어 cast도 지원 
}


reinterpret_cast #

다른 타입간의 형변환에 사용됩니다.
일반적으로 일반타입과 포인터간 혹은 서로 다른 타입의 포인터간 cast에 사용됩니다.
int main()
{
  int n = 90;
  char *p = reinterpret_cast<char *>(&n);  // 서로다른 포인터 

  int nHandle = reinterpret_cast<int>(p);  // 포인터와 일반타입 
}


const_cast #

const, volatile 두개의 키워드로 선언된 변수의 속성을 제거하는 cast 입니다.
const_cast는 상수성및 휘발성의 성질을 제거할때만 사용되고 다른 현변환이나
클래스계층간 cast할수는 없습니다.
int main()
{
  const char sz[] = "TEST";
  char *p = const_cast<char *>(sz);
}


dynamic_cast #

static_cast와 상대적인 의미의 cast로서 클래스의 안전한 downcast에 이용됩니다.
dynamic_cast는 컴파일시 타입을 체크하는것이 아니고 런타임시 포인터의 타입이 아닌
실제로 가르키고있는 객체의 계층간 타입을 확인해 cast를 해주는 연산자이고
그로인해 약간의 오버헤드가 존재 합니다.

dynamic_cast의 기본개념은 virtual function table에 있는 들어있는 타입의 정보를
이용하기 때문에 virtual member function을 가지고 있는
Base클래스에서 상속받은 객체로의 cast만 가능합니다.

그리고 오버헤드로 인하여 몇몇 컴파일러들은 기본으로 이 기능을 끄고 사용할때 옵션으로서
기능을 켜고 사용할수 있게 제공됩니다
(대표적으로 VC++이 여기에 속합니다. VC++에서 dynamic_cast를 이용하려면
Project 세팅에서 옵션을 체크해줘야 됩니다.)

dynamic_cast의 행동은
포인터는 cast가 성공일 경우 해당 포인터를 cast가 실패일 경우 NULL로 세팅을 합니다.
참조의 경우는 cast가 실패하면 bad_cast Exception 이 발생합니다.
class NotVBase
{
public:
  ~NotVBase() {}
};
class NotVDerived : public NotVBase {};

class VBase
{
public:
  virtual ~VBase() {}
};
class VDerived : public VBase{};

int main()
{
  NotVBase *p = new NotVDerived;
  // ERROR   
  // 컴파일러에서 에러를 잡아 줄것 입니다. 
  // Base 클래스가 virtual member 가 존재하지 않습니다. 
  NotVDerived *pp = dynamic_cast<NotVDerived *>(p);   // 컴파일 에러   


  VBase *vp = new VDerived;
  VBase *nvp = new VBase;

  VDerived *vpp1 = dynamic_cast<VDerived *>(vp); // SUCCESS   

  // nvp가 가르키고 있는 포인터는 VDerived 타입이 아닙니다. 
  // 따라서 vpp 에는 NULL이 세팅됩니다.   
  VDerived *vpp2 = dynamic_cast<VDerived *>(nvp);
  if (!vpp2) {
    // 이부분이 실행됩니다.   
  }

  try {
    // 만약 참조로 cast하다 실패하면 bad_cast Exception이 발생합니다. 
    VDerived &vd = dynamic_cast<VDerived &>(*nvp);
  }
  catch (...) {}

}

dynamic_cast는 계층간의 downcast는 물론
다중상속으로 인한 Base 클래스끼리의 형변환도 해당이 됩니다.

class Base1
{
public:
  virtual ~Base1() {}
};

class Base2
{
public:
  virtual ~Base2() {}
};

class Derived : public Base1, public Base2
{

};

int main()
{
  Base1 *p1 = new Derived();

  // Base1 에서 Base2로의 형변환 
  // p1이 가르키고 있는 타입은 Derived 
  Base2 *p2 = dynamic_cast<Base2 *>(p1); // SUCCESS 

}

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

template  (0) 2008.05.01
functor - 함수객체  (0) 2008.05.01
export  (0) 2008.05.01
main function  (0) 2008.05.01
tr1::regex (boost::regex) 샘플코드  (0) 2007.10.21

export #

C++ template의 내보내기 키워드 입니다.
아직 제대로 구현되어 있는 컴파일러는 없습니다.


template은 타입이 정의 되지 않은 상태의 함수나 객체를 컴파일 타입에 정의를해서 코드를 생성하는 일종의 코드제너레이터 같은 것입니다.
template <class T>
class TClass
{
  T t;
};

위의 코드는 TClass<int> intClass 란 식으로 인스턴스를 만들기 전까지는 혹은 함수 같은 경우 사용되지 전까지는 코드를 만들지 않습니다.

아마 아래와 같은 코드를 컴파일 당시 만들것입니다.

class TClass
{
  int t;
};

그도 그럴것이 타입을 모르는데 어떻게 코드를 만들겠습니까?
그렇다고 모든 타입에 대해서(사용자정의 타입까지 한다면 헉..)다 코드를 만들수는 없는 노릇이고 ...

Object 파일 #

Object 파일 즉 중간 파일은 바이너리 코드 입니다.
컴파일된 기계코드이지요
이미 그안에는 실행되어지는 내용들이 있습니다.

그런데 template으로 라이브러리를 만들어서 Object 파일을 만든다면 ...
과연 타입이 없는데 어떤식으로 코드를 생성해서 놓을까여 ????

export는 그 답을 주는 키워드 입니다.
export는 template 내용을 Object파일에 넣어서 재사용 할수 있도록 하는 도구 입니다.

그러나 위에서 말한것 처럼 구현하기가 쉬운일은 아닌거 같습니다.
많은 비용도 들고 ...
이런 이유에서 컴파일러사 마다 쉽게 구현 못하는 이유일꺼라고 생각합니다.

아직 VC++이나 Borland C++, GNC C++ 의 대표적인 컴파일러 회사들의 구현물들도 아직 그것을 구현하지 못하고 있습니다.

아래의 링크 밑에 부분에 "export 및 내보낸 템플릿" 이란 주제로 Bobby Schmidt이란 사람이 글을 써놓은것이 있습니다.
한마디로 끔찍하다는 표현을 -_-

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

functor - 함수객체  (0) 2008.05.01
Casting operators  (0) 2008.05.01
main function  (0) 2008.05.01
tr1::regex (boost::regex) 샘플코드  (0) 2007.10.21
boost 를 이용한 TR1 Library 사용하기  (0) 2007.10.10

main Function #

ISO C++의 공식 main함수의 원형은 아래와 같습니다.

int main()
int main(int argc, char *argv[])

두개의 원형이 아닌이상 모두 표준에 어긋나는것입니다.
표준화이전에 쓰여진 책 혹은 컴파일러에서는 void main() 이란 원형도 타탕하다는 주장이 있었습니다.
그러나 그것은 표준에 어긋납니다.
VC++ 같은 컴파일러는 void main()을 허용하지만 하위호환성을 위해 남겨 놓은 것 뿐입니다.


return #

표준 main함수는 int를 리턴하게 되어 있습니다.
int main()
{
  return 0;
}

허나 ISO C++에서는 main함수에서의 명시적인 리턴이 없으면 자동으로 return 0;를 자동으로 넣어줍니다.

// 이 main은 타당합니다. 
// 컴파일러에 의해 return 0; 가 수행됩니다. 
int main()
{
}

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

Casting operators  (0) 2008.05.01
export  (0) 2008.05.01
tr1::regex (boost::regex) 샘플코드  (0) 2007.10.21
boost 를 이용한 TR1 Library 사용하기  (0) 2007.10.10
VC++ 2005 배포  (0) 2007.06.22

FFmpeg : http://ffmpeg.mplayerhq.hu/
Unofficial FFmpeg Win32 Builds :  http://arrozcru.no-ip.org/ffmpeg_builds/


flv 변환예제
ffmpeg.exe  -y -i input.wmv -ar 22050 -qmax 10 output.flv

-ar rate            set audio sampling rate (in Hz)
-qmax              <int>   E.V.. max video quantizer scale (VBR)


이미지 추출 예제
ffmpeg.exe -y -i "%1" -ss 0:0:10.0 -vframes 1 -vcodec png -y -f image2 "%1.png"

-ss time_off        set the start time offset
-vframes number     set the number of video frames to record
-vcodec codec       force video codec ('copy' to copy stream)


'Dev > Windows' 카테고리의 다른 글

유니코드(UNICODE), C++  (0) 2008.10.16
VARIANT 에서 객체 추출하기  (0) 2008.09.09
벤치마킹  (0) 2007.10.17
달라진 기능 (IIS 6.0)  (0) 2007.09.28
SQLOLEDB Provider 의 특이한 동작  (0) 2007.09.20

#include <iostream>
#include <string>
#include <memory>
#include <fstream>
#include <iterator>
#include <sstream>
#include <vector>
#include <functional>
#include <regex>
using namespace std;


typedef vector<string> MatchList;
typedef MatchList::iterator MatchListIter;


bool MatchFunc(const boost::smatch &m, MatchList *p)
{
    p->push_back(m.str());
    return true;
}


void RegExTest(const char *pFileName)
{
    ifstream fin(pFileName);
    if (!fin) {
        cout << "file open error !!" << endl;
        return;
    }


    ostringstream oss;
    copy(istreambuf_iterator<char>(fin), istreambuf_iterator<char>(), ostreambuf_iterator<char>(oss));


    tr1::regex expr;
    expr.assign("<[a-zA-Z\\s\\/][^>]*>");    // html 표현식

   
    // replace 매치되는 문자열 치환
    string strResult = boost::regex_replace(oss.str(), expr, "");

    MatchList mlist;


    // grep 매치되는 문자열 찾기
    int nMatch = boost::regex_grep(tr1::bind(MatchFunc, _1, &mlist), oss.str(), expr);

    MatchListIter it = mlist.begin();
   
    for (; it != mlist.end(); ++it) {
        cout << *it << endl;
    }
}

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

export  (0) 2008.05.01
main function  (0) 2008.05.01
boost 를 이용한 TR1 Library 사용하기  (0) 2007.10.10
VC++ 2005 배포  (0) 2007.06.22
Unhandled C++ Exceptions  (0) 2007.06.19

- 1초동안 실행한 횟수

- 함수
  Add : 두수 더하기
  AddData : DB에 데이터 Insert

- 절대적인 숫자의 크기는 의미없음 (상대적인 수치만 보기)


COM+ 벤치마킹

  서버 응용프로그램
클라이언트 언어 호출방법 Add AddData
VC++
(MFC)
ATL/C++, COM+ Custom 10277 381
ATL/C++, COM+ Dispatch 3838 346
C#, COM+ Dispatch 1521 373
C# (WinApp) C#, COM+   1410 384


  라이브러리 응용프로그램
클라이언트 언어 호출방법 Add AddData
VC++
(MFC)
ATL/C++, COM+ Custom 36757 67
ATL/C++, COM+ Dispatch 15895 65
C#, COM+ Dispatch 2021 411
C# (WinApp) C#, COM+   4177 511


  일반 라이브러리 
C# (WinApp) C#, Library     639
VC++ (MFC) VC++, DLL     916



ATL COM+ (VC++)

- Unmanaged (Native) Custom 인터페이스로 호출시 호출성능은 가장 좋음

- 라이브러리 응용프로그램 패키지에서 호출성능은 가장 좋으나

  AddData 의 경우 DB 풀링이 안되는거 같아 성능 저하 현상

- .Net 클라이언트 프로그램에서 호출했을 경우도 Iterop 에 의한 성능저하 별로 없음


: 서버 응용프로그램 패키지에서 실행 권장

: 어플리케이션 프로그램이 아닌 웹환경에서는 대세 



C# COM+ (.Net Enterprise Service)

- Unmanaged (Native) 코드보다는 호출성능은 떨어짐

- 그러나 DB업무인 AddData의 경우 Unmanaged 코드랑 성능차이 없음

- AddData 의 경우 라이브러리 응용프로그램 패키지에서 성능 향상   

  (ATL COM+ 와 대조적인 결과, DB풀링을 드라이버차원이 아닌 .Net 어셈블리에서 처리하는것 같음)

- Unmanaged 클라이언트 프로그램에서 호출했을 경우 Iterop에 의한 약간의 성능 저하

- Managed (.Net)에서 호출시 가장 좋은 성능을 보임

- DTC를 사용하지 않을경우 .Net Library 를 사용하는것이 COM+를 이용하는것 보다 성능이 좋음


: 라이브러리 응용프로그램 패키지에서 실행 권장

: COM+ DTC를 이용하지 않을시는 .Net Library 권장








DCOM & RDS


- 인터페이스
    DCOM(Proxy) - 컴포넌트 내보내기/설치하기 Custom 인터페이스 사용
    DCOM(RDS.Dataspace) - RDS.Dataspace 이용 하여 Dispatch 인터페이스 사용
    RDS - RDS.Dataspace

  Add AddData
DCOM(Proxy) 596 200
DCOM(RDS.Dataspace) 295 146
RDS 42 38


'Dev > Windows' 카테고리의 다른 글

VARIANT 에서 객체 추출하기  (0) 2008.09.09
ffmpeg  (0) 2008.04.09
달라진 기능 (IIS 6.0)  (0) 2007.09.28
SQLOLEDB Provider 의 특이한 동작  (0) 2007.09.20
COM+ Queued Components  (0) 2007.08.09

+ Recent posts