반응형

연산자 오버로딩의 경우에는 개인적으로는 구현을 해볼 일이 없었다.
엔진을 사용하기 때문에 기본적인 자료형들의 연산자들은 구현이 되어 있었기 때문이다.
최근에 연산자 오버로딩을 하는 부분을 본 것은 stream처리를 하는 부분을 추가할 때 정도였다.
개인적으로는 많이 사용하지는 않기 때문에 간략히 정리해보고자 한다.

항목5 - 사용자 정의 타입변환 함수에 대한 주의를 놓지 말자.

암시적 타입변환은 원하든 원하지 않든 컴파일러에 의해 자동으로 일어난다.
암시적 타입변환을 하는 함수는 두 가지가 있다.

단일 인자 생성자

template<class T>
class Array
{
  public:
    Array(int lowBound, int highBound);
    Array(int size);

    T& operator[] (int index);
    ...
}

bool operator == (const Array<int>& lhs, const Array<int>& rhs);

Array<int> a(10);
Array<int> b(10);

for (int i = 0; i < 10; ++i)
{
  if (a == b[i])
  {
  }
  else
  {
  }
}

a == b[i]는 오타가 나서 컴파일 에러가 나기를 원한다.
하지만 b[i]는 int 이므로 임시로 Array 생성자로 변환된다.

위와 같은 생성자에는 explicit를 넣어주자.

암시적 타입변환 연산자

class Rational
{
  public:
    ...
    operator double() const;
    ... 
}

Rational r;
double d = 0.5 * r;

cout << r;

<< 연산자를 구현하지 않았지만 동작을 한다!
원하는 결과는 아닐 수 있다.

컴파일 에러를 원한다.

이를 막기위해 암시적 타입변환 연산자를 제거하고 같은 기능을 하는 함수를 제공하자.
asDouble()과 같은 함수.

항목6 - 증가 및 감소 연산자와 전위, 후위 형태를 반드시 구분하자.

전위, 후위 증감 연산자는 인자를 넘기지 않는다.
그래서 약속된 형식이 아래와 같다.
후위 ++의 경우에는 숫자 0을 넘기는 식으로 구분하게 되었다.

class UPInt
{
  public:
    UPInt& operator++();                // 전위++
    const UPInt operator++(int);      // 후위++
    ...
}

UPInt& UPInt::operator++()
{
  *this += 1;
  return *this;
}

const UPInt UPInt::operator++(int)
{
  const UPInt oldValue = *this;
  ++(*this);
  return oldValue;
}

후위 연산자의 경우에 const를 리턴하지 않는 경우에는 i++++과 같은 이상한 문법이 가능해진다.
i.operator++(0).operator++(0); 과 같다.
그리고 oldValue와 같은 임시객체가 필요한것도 마음에 들지 않는다.
그렇기 때문에 가능하면 전위 연산자를 사용하도록 하자!
또한 구현의 일치성을 보장하기 위해 후위 연산자에서 위의 예제와 같이 전위 연산자를 호출 해주는게 좋다!

항목7 - &&, ||, 혹은 . 연산자는 오버로딩 대상이 절대로 아니다.

복잡한 조건을 검사할 때 단축처리가 가능하다.
가령 if ((p != 0) && (strlen(p) > 0)) 와 같은 조건이 있다면 p가 0(nullptr)이면 strlen()이 호출되지 않는다.

그런데 우리가 굳이 위의 &&, || 연산자를 재정의 한다면 위의 단축처리가 불가능하다.

if (expression1 && expression2)
-> expression1.operator&&(expression2)
-> operator&&(expression1, expression2)

둘 중의 하나로 해석될 것이기 때문이다.

즉 expression2가 실행이 된다는 이야기다.

, 연산자의 경우에도 마찬가지다.

항목8 - new, delete의 의미를 정확하게 구분하고 이해하자.

new 연산자의 동작

  1. 메모리 할당
  2. 객체의 생성자 호출

오버로딩할 수 있는 부분은 메모리를 할당하는 부분이다.

void* operator new(size_t size);

메모리 지정 new (placement new)

이미 할당된 메모리에 new 연산자를 사용할 수 있다.
예를 들면 new (buffer) Widget (widgetSize);

메모리를 받아서 객체를 생성해주는 기능을 만들때 사용하면 된다.

void* operator(size_t size, void* location);

#include <new>를 추가해주면 사용 가능하다!

delete 연산자의 동작

  1. 객체의 소멸자 호출
  2. 메모리 제거

  • 메모리 지정 new를 이용해 생성된 객체를 delete로 지우지 말자.
    • 메모리가 어디서 할당되었는지 모르기 때문이다.
    • 메모리 지정 new를 이용했다면 메모리 생성, 해제하는 기능이 어딘가 있을테니
    • 소멸자 호출 후에 이를 이용해 메모리를 해제해주자.

배열의 할당 해제

opearator new[], delete[]는 오버로딩 가능하기는 하지만
쉽지 않기 때문에 사용하지 않는게 좋겠다 ㅋㅋㅋ

반응형
Posted by msparkms
,