본문 바로가기

Dev/C++

operator overloading 1

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
operator overloading 1  (0) 2008.05.01
Conversion Functions - 변환함수  (0) 2008.05.01
explicit  (0) 2008.05.01
template  (0) 2008.05.01