본문 바로가기

Language/C++

단항 연산자 오버로딩

단항연산자 오버로딩

단항연산자 오버로딩은 이항연산자랑 조금의 차이를 보인다.
단항연산자는 피연산자가 하나 밖에 없기 때문에 멤버 함수는 메개변수를 받지 않는다.
또한 일반 전역 함수로 오버로딩을 하게 되면 메개변수는 하나만 받는것!(머...기본이지 이건.ㅋ)

하지만 차이를 보이는 부분이 상당하다.
기본적으로(A라는 클래스가 있고 x와y 멤버를 가지고 있다고 가정)
멤버 함수라고 한다면...
A& operator++()//매개변수없다. 또한 리턴값이 레퍼런스이다.
{
    x++;
    y++;
    return *this;//리턴값이 자기자신이다. 자기자신의 주소를 역참조 하기 때문이다!
}

일반 전역 함수라고 한다면...
클래스 내부
    friend A& operator++(A& p);//매개변수가 한개이다.
함수 부분
A& operator++(A& p)
{
    p.x++;
    p.y++;
    return p;//여기선 this하면 않된다~ 매개변수로 받아온p자체를 참조하여 리턴해야한다.
}

자기 자신을 참조 하여 리턴 한다는 것은, 리턴 값을 변경 하게 되면 본체또한 변경을 무릅쓰겠다 라는 것이다.
이렇게 만들어진 이유는 상당히 간단한데 만일..예를 들어서

A a;
++a; 라고 메인함수에 코드를 작성하면 별로 문제 될껀 없다. 이때는 void로 리턴해도 별다를꺼 없다^^
하지만..
++(++a); 라고 코드를 작성했다고 가정하면 문제가 발생된다.
괄호안에 ++a를 하고 리턴값을 일반적으로 받아 버리면 (예를 들어서 그냥 * 로 리턴값을 받으면) 이는 복사본이
리턴되어 복사본이 괄호 바깥에 있는 ++ 연산을 하게 된다.
그렇게 되면 본체는 한번의 증가로 더이상 증가를 하지 않는다. 이는 기본 문법에 어긋나는 문법이 되어버린다.
참고로 리턴문 return 또한 함수호출시 매개변수를 call by value 형식과 같이 리턴을 하게 된다.(포인터로 리턴을
해도 역시 복사본이 리턴됨을 명심하자^^)
하지만 레퍼런스 즉, 참조로 리턴을 하게 되면, 본체자체를 제어 하는 거이기 때문에 괄호 안의 연산을 한후 바깥의 연산도
본체가 받게 되므로 데미지는 크리티컬로 뜰것이다.ㅋㅋㅋㅋ(젠장...게임하고싶어.ㅠ)
또한 조심하여야 할것은 일반 전역 함수로 단항 연산자를 오버로딩 했을때는 그냥 넘어온 매개변수 p를 리턴 하면 된다는
것이다!

예문 소스이다^^
#include <iostream.h>

class Point
{
private:
    int x, y;
public:
    Point(int _x = 0,int _y = 0):x(_x), y(_y)//멤버 이니셜라이져.
    {
    }
    void ShowPosition();
    Point& operator++();//메개변수가 필요가 없다.
    friend Point& operator--(Point& p);//피연산자가 하나이기 때문에 매개변수도 하나만..
};

void Point::ShowPosition()
{
    cout<<x<<" "<<y<<endl;
}

Point& Point::operator++()//피연산자가 하나기 때문에 매개변수는 void이다.
{//리턴 타입이 & (참조) 로 리턴을 한다.
    x++;
    y++;

    return *this;//증가된 객체의 자기자신을 참조한다.
}

Point& operator--(Point& p)//const키워드를 적지 않는다. 피연산자가 하나기 때문에 하나만 받는다!
{//리턴 타입이 & (참조) 로 리턴을 한다.
    p.x--;
    p.y--;

    return p;//전역함수이기 때문에 this가 아니라 받아온 매개변수 p를 넘겨서 p자체를 리턴한다.
}

int main()
{
    Point p(1,2);
    ++p;//x,y의 값을 1씩 증가시킨다.
    p.ShowPosition();
    --p;
    p.ShowPosition();
    ++(++p);
    p.ShowPosition();
    --(--p);
    p.ShowPosition();

    return 0;
}

만약 자신이 만든 클래스를 가지고 코드를 작성할때 "난 두번이상 할 필요없으므로 그냥 void로 리턴할랭~" 한다면
코드의 안정성이 떨어질수가 있다. 해야하는건 귀찬아도 해야한다. (않그럼 #include<stdio.h>를 자동으로 해놓을수도 있지 않겠는가?~ㅋ)

단항 연산자의 기능중 전 연산과 후 연산이 있다.
이를 해결하기 위해서 한번더 !약속! 이라는 것을 했다. 그것은 바로 전 연산자 ++p1 이면 그냥 원래와 동일하게
p1.operator++();
이라 정의하고, 만일 p1++ 이면
p1.operator++(int); 라고 한것이다. 여기서 int는 매개변수 개념이 아닌 단지 전.후 연산을 구분 짓기 위함이다.

전.후 단항 연산자 처리.
class Point
{
private:
    int x, y;
public:
    Point(int _x = 0,int _y = 0):x(_x), y(_y)//멤버 이니셜라이져.
    {
    }
    void ShowPosition();
    Point& operator++();//++a 라는 의미 먼저 연산을 한다.
    Point operator++(int);//a++ 라는 의미
};

void Point::ShowPosition()
{
    cout<<x<<" "<<y<<endl;
}

Point& Point::operator++()//전 연산을 처리하는 단항 연산자 오버로딩 함수이다. 동일하다^^
{//리턴 타입이 & (참조) 로 리턴을 한다.
    x++;
    y++;

    return *this;//증가된 객체의 자기자신을 참조한다.
}

Point Point::operator++(int)//참조로 리턴을 할수 없다. temp가 지역 변수(지역객체) 이기 때문이다.
{//리턴 타입이 & (참조) 로 리턴을 한다.
    Point temp(x,y);//Point temp(*this); 증가하기 전에 미리 temp에 값을 복사 한다.
    x++;//복사후 증가.
    y++;

    return temp;//복사한 temp 를 리턴한다.
}

int main()
{
    Point p1(1,2);
    (p1++).ShowPosition();
    p1.ShowPosition();

    Point p2(1,2);
    (++p2).ShowPosition();
    

    return 0;
}

레퍼런스로 참조 할수 없는것은 지역변수 이다. 객체도 불변이다. 지역변수든 지역 객체이든 함수가 끈나면 본체가 소멸하기 때문이다.
그렇기 때문에 일반 클래스 타입으로 리턴 하게 된다. 이는 연산자 오버로딩의 한계라고 볼수 있다.
또한
Point temp(x,y); 문장은 주석에도 나와있듯이 값을 먼저 temp에 복사한후 증가를 시키고 리턴은 증가 되지 않은temp의 값을 리턴하는 것이다.
이렇게 되면 리턴될 시에는 값이 증가 하지 않았지만 리턴문 다음에는 증가된 값을 가지고 있는 것이다. 이건 일종의 훼이크^^.
하지만 연산자 오버로딩의 한계의 중요한것은 후 연산시 (a++)++; 문장은 할수 없다는 것이다. 이유는 역시 간단하다. 리턴값이 참조된 실객체가 아니라
복사된 객체이기 때문이다. 이건 좀..섭섭하다. 하지만 쓸일없다ㅡ_ㅡ;;