연산자_오버로딩 : 함수 오버로딩 규칙을 연산자에 적용하는 문법 #functor 사용시에 자주 사용할 수 있음 혹은 모던 C++에선 람다함수 로 대체할 수 있음.

연산자 좌측에 있는 객체 기준으로 동작한다.

C++의 객체가 없으면 사용할 수가 없다.

class CObj{
public: 
   CObj(int a, int b) : m_iA(a), m_iB(b){};
private:
   int m_iA;
   int m_iB;
}

int main(){
   CObj Dst(10,20);
   CObj Src(30,50);
   Dst = Dst + Src; //왼쪽 객체 기준(즉, Dst가 호출중)
}

위와 같은 상황에 대응하기 위한 문법. 직접 연산자와 같은 이름의 함수를 사용해서 원하는 기능을 직접 구현해보자!

operator : 연산자 형태의 이름을 가진 함수를 만드는 키워드

더하기 구현 예제

//CObj 클래스 내부
CObj operator + (const CObj& rhs){
   CObj Result (m_iA + rhs.m_iA, m_iB+rhs.m_iB);
   return Result;
}

오버로딩은 좌측의 객체를 기준으로 동작한다. 그래서 Dst + 40이 아니라 40 + Dst라면?

  1. 기본 오퍼레이터가 구현되어 있어야 한다
  2. 클래스 외부 전역 함수로 아래와 같이 구성한다.
CObj operator+(const CObj& lhs, const CObj& rhs) {
    return CObj(lhs.m_iA + rhs.m_iA, lhs.m_iB + rhs.m_iB);
}

클래스 멤버로만 구현 가능한 연산자 `= , () , [] ,

  1. 대입연산자 (오른쪽에서 왼쪽으로 움직임)
  2. 함수 호출연산자 (함수 이름은 왼쪽에 있음)
  3. 대괄호 연산자 (함수 이름은 왼쪽에 있음)
  4. 포인터 연산자 (함수 이름은 왼쪽에 있음)

디폴트 대입 연산자 !반환타입이 레퍼런스 Dst = Dst + Src; 자기 자신에게 대입하는 것이기 때문에

CObj& operator = (const CObj& rhs){
   m_iA += rhs.m_iA;
   m_iB += rhs.m_iB;
   return *this;
}

만약 멤버 함수 중에 포인터가 있다면, 그 포인터를 얕은 복사가 일어나지 않도록 고려해야 한다.


C언어 구조체 안에 string을 쓰면 안됨. 왜냐면 생성자와 같은 원리가 없기 때문에.

~ string은 대입연산자가 깊은 복사 형태로 동작함.

new와 delete도 연산자 오버로딩이 가능함. +) cout << 같은 것도 오버로딩 된 것임.


단항 연산자 오버로딩

전위 연산 : 반환타입은 자기 자신을 불러서 증가(선연산 후대입)

CObj& operator ++(){
   //내부 멤버 증가시키는 내용
   return *this;
}

후위 연산 : 반환타입은 객체 타입:: 복사본 (선 대입 후 연산)

CObj operator ++(int){
   CObj tmp(*this); //복사 생성자
   //this의 내부 멤버 증가시키는 내용
   return tmp;
}

매개 변수 안에 int는 후위연산을 알려주는 문법임. 후위연산은 복사본을 반환하기 떄문에 중첩이 되지 않음. (Dst++)++ 이거 원래도 안되는 거임.

연산자 오버로딩의 사용처! 보통 함수 객체!


functor : 함수 객체 - 객체를 함수처럼 사용하는 문법 () 소괄호 연산자를 오버로딩하여 사용. STL 알고리즘의 “조건자” 용도로 사용.

ex) 숫자를 정렬을 할 때, 오름차순으로 할 것인지? 내림차순으로 할 것인지? 이것을 결정할 때 사용할 수 있는 것이 조건자다. 이건 람다식으로 대체 가능 (성능은 람다식이 더 좋다.)

class Plus{
public:
   int operator()(int Dst, int Src){
      return Dst+Src
   }
}

int main(){
   Plus Functor;
   cout << Functor(10,20)<<endl;
}

객체를 함수처럼 사용하는 모습;

함수객체를 임시 객체화 시켰을 때 함수 호출하는 것보다 성능이 더 좋다 cout << Plus(10,20)<<endl; : 임시객체화

STL 조건자는 bool타입을 반환하는 형식의 함수 포인터를 사용하기 때문에 함수객체를 사용하면 내가 구현한 함수 객체를 들여보내서 구현할 수 있음.

class CSort{
public:
   virtual bool operator()(int Dst, int Src) =0
}

//오름차
class ASC : public CSort{
   bool operator()(int Dst, int Src) override{
      if(Dst > Srd) return true; //좌가 우보다 크면 바꾸고
      return false; //아니면 바꾸지 마라
   }
}

//내림차
class DSC : public CSort{
   bool operator()(int Dst, int Src) override{
      if(Dst > Srd) return false; //좌가 우보다 크면 바꾸고
      return true; //아니면 바꾸지 마라
   }
}
void BubbleSort(int pArrp[], int iSize, CSort& Functor){
   for(int i=0; i< iSize; ++i){
      for(int j=0; j<iSize-1; ++j){
         //조건자 역할
         if(Functor(pArr[j],pArr[i]+1)){
            swap(pArr[i],pArr[j]);
         }
      }
   }
}

위 두개를 엮어서 본다면, 함수객체를 조건자로 사용하는 것을 알 수 있다. 또한 불필요하게 스택 메모리를 사용하지 않도록 객체를 생성하지 말고 임시 객체를 사용해서 진행하는 것이 권장된다.

BubbleSort(배열,사이즈,DSC());

임시객체 : 이름 없이 임시 메모리 공간에 잠깐 할당되었다가 소멸되는 객체

근데 원래는 되는데, 컴파일러 업데이트와 모던 C++로 인해 임시 객체의 사용이 막히게 됨 만약 직접 사용하고 싶다면 R_Value_Reference 문법을 사용해야 함 (&&)

이전에 함수 포인터를 사용할 때에는 조건자로 함수를 넣게 되면 함수 포인터에 “함수 이름”을 넘겨주어야 하기 때문에 매개 변수를 함께 넘겨줄 수가 없었다. (함수 이름만 줘야 하니까.)

그러나 함수 객체는 생성자 오버로딩을 통해서 매개 변수를 함께 넘겨줄 수 있기 때문에 더욱 효율적이다. 그러나 람다 함수 이후로는 의미가 좀 퇴색되었지만…


cout의 동작 방식

# include <stdio.h>

namespace mystd{
   class Ostream{
   public :
      Ostream& operator <<(int iData){
         printf("%d,iData);
         return *this;
      } 
   }
}

요지는 우리가 사용하던 std::cout도 그저 클래스로 만들어져 있을 뿐이라는 것이고 <<연산자를 활용한 것이다. iostream도 마찬가지.

숙제 25-03-24 string 연산자 직접 구현