18. 연산자 오버로딩

TCPL++ 18장

18.2 연산자 함수

18.2.1 이항 연산자와 단항 연산자

  • 기본적으로 이항 연산자를

  • 이항 연산자(aa@bb)는 다음과 같이 2가지 형태로 해석된다.

    • aa.operator@(bb)(멤버함수)

    • operator@(aa,bb)(비멤버함수)

class X {
public : 
	void operator+(int); 
	X(int);
};

void operator+(X,X);
void operator+(X,double);

void f(X a)
{
	a+1;     // a.operator+(1)
	1+a;     // ::operator+(X(1), a)
	a+1.0;   // ::operator+(a,1.0)
}
  • 전위형 단항 연산자(@aa) 는 다음과 같이 2가지 형태로 해석된다.

    • aa.operator@()(멤버함수)

    • operator@(aa)(비멤버함수)

  • 후위형 단항 연산자(@aa) 는 다음과 같이 2가지 형태로 해석된다.

    • aa.operator@(int)(멤버함수)

    • operator@(aa, int)(비멤버함수)

18.2.2 연산자의 사전 정의된 의미

  • 일부 기본 제공 연산자는 동일한 인자들에 대해 다른 연산자들의 조합과 같은 의미를 지니게 된다. → 예를 들어 a 가 정수라면, ++aa+=1 을 의미한다. → 이는 결국 a = a+1 을 의미한다.

  • 이런 관계는 사용자가 정의한 연산자에 영향을 받지 않는다. → 예를 들어 컴파일러는 operator+()operator=() 의 정의에서 operator+=() 의 정의를 알아서 생성해내지 못한다.

18.2.3 연산자와 사용자 정의 타입

  • 연산자 함수는 멤버이거나 사용자 정의 타입의 인자를 최소 한 개는 받아들여야 한다.

  • 자신의 첫 번째 피연산자로 기본 제공 타입을 받아들이게 만들어진 연산자 함수는 멤버 함수가 될 수 없다. → 2.operator+(aa) 를 의미하게 정의해주는 int 클래스가 없기 때문이다. → 설사 있더라도, 2+aaaa+2 를 처리하려면 두 개의 다른 멤버 함수가 필요할 것이다.

18.2.4 개체 전달

  • 연산자를 정의할 때 대개 a=b+c 같이 관용적 표기법을 제공하고 싶어 한다.

  • 이를 위해서, 인자를 전달하는 방법과 리턴값을 전달하는 방법에는 제한이 있다.

  • 먼저 인자를 전달하는 방법에는 두 가지 선택이 있다.

    • 값에 의한 전달

      • 예를 들어 작은 개체에 대해서는 값에 의한 전달이 대개는 현실적이고, 가장 최적의 성능을 보인다.

    • 참조에 의한 전달

      • 좀 더 큰 개체는 참조에 의해서 전달된다. → 인자를 변경하지 않기로 되어있는 개체 전달에는 const 를 사용한다.

  • 대개 연산자는 결과를 반환한다. 새로 생성된 개체의 포인터나 참조자를 반환하는 것은 대체적으로 좋지 못한 생각이다. → 포인터를 사용하면 표기 문제(관용적인 표기로 사용하기 어렵다)가 발생하고, 힙 영역에 있는 개체의 참조는 메모리 관리 문제도 낳는다.

  • 이후 내용은 멘토링 이후 추가 …..

18.3 복소수 타입

  • 복소수(complex) 타입을 연산자 오버로딩을 통해 구현해보자

  • 복소수는 실수와 허수가 결합된 형태를 의미하며, 만약 복소수 타입을 만든다면, 아래와 같이 사용하기를 원할 것이다.

18.3.1 멤버 및 비멤버 연산자

  • 필자는 개체의 표현을 직접적으로 조작하는 함수를 최대한 자제하는 편인데, → + 연산자 내부에서 += 연산이 이루어지도록 설계하자

  • 이를 달성하려면, 클래스 자체 내에서는 += 같이 태생적으로 자신의 첫 번째 인자의 값을 변경하는 연산자만을 정의해야 한다.

  • 그렇다면 + 같이 자신의 인자의 값을 기반으로 새로운 값을 산출하는 연산자들은 클래스 외부에서 정의돼야 한다.

  • 위 선언이 주어지면 아래와 같이 사용할 수 있다 . → r1r2 의 효율성 차이를 제외하면 연산을 동일하게 동작한다.

18.3.2 혼합 모드 산술 연산

  • zcomplex 인 경우에 2+z 를 처리하면서 연산자 + 가 서로 다른 타입의 피연산자를 받아들이게 정의해야 한다. → **혼합 모드 산술 연산(mixed-mode arithmetic)**이 필요하다는 것이다!

  • 이를 위해 아래와 같이 적절한 연산자를 추가해주면 된다.

  • operator+() 의 세 가지 변형은 complex 바깥에서 정의될 수 있다.

    • 에 대해서 이런 선언들이 주어지면 다음과 같이 작성할 수 있다.

18.3.3 변환

  • complex 변수의 대입과 초기화를 처리하기 위해서는 정수 또는 부동소수점 숫자를 complex 로 변환해야 한다.

  • 그것이 가능하기 위해서는 다음과 같이 하나의 인자를 받아들이는 생성자를 제공해야 한다.

    • 생성자 기본값을 사용해 코드를 줄이자

    • 해당 생성자에서는 암시적으로 변환이 되는데, 암시적인 변환을 허용하고 싶지 않다면, explicit 로 선언하면 된다.

18.3.3.1 피연산자의 변환

  • 위에서 혼합 모드 산술 연산을 아래와 같이 구현했다.

  • 하지만 이런식으로 타입이 늘어날 때마다 연산자 함수가 늘어나는 것은 유지보수가 어려워진다.

  • 때문에, 변환을 이용해서 유연하게 해결해나가야 한다.

    • 현재 우리의 complex 클래스는 double 을 complex 로 변환하는 생성자를 하나 가지고 있다.

  • 따라서 complex 에 대해 동등 연산자 버전을 딱 하나만 선언할 수 있다. → 물론 변환에 의한 오버헤드가 생겨날 수 있지만, 그런 이슈가 중요하지 않은 경우에는 충분히 변환을 고려할 수 있다. → 암시적 변환을 원하지 않는다면 explicit 을 사용할 수 있다.

18.3.4 리터럴

  • 기본 제공 타입으로 된 리터럴이 있다.

    • 예를 들어 1.212e3 은 double 타입의 리터럴이다.

  • complex 에 대해서도 생성자 constexpr 을 선언하면 기본 제공 타입의 리터럴과 비슷해진다.

  • 해당 코드는 complex 도 기본 제공 타입의 리터럴과 마찬가지로 컴파일 타임에 구성 부분을 기반으로 생성될 수 있다.

    • 생성자가 간단하고 인라인인 경우(특히, constexpr 인 경우) 리터럴 인자를 통한 생성자 호출을 그냥 리터럴로 보아도 무방하다.

Last updated