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

+ Recent posts