연산자 오버로딩 - '=', '[]'

3763 단어 cppcpp

어제 마무리하고 싶었던 부분인데 못끝낸 부분이다.
생각보다 연산자 오버로딩의 양이 많다.


대입 연산자

한창 constructor 얘기할 때 복사 생성자 얘기를 많이 했었다.
그 당시 복사 생성자는 객체의 생성과 동시에 대입될 때 실행된다고 배웠다.

class cl
{...}

cl obj1;
cl obj2 = obj1;

cl obj1;
cl obj2;
obj2 = obj1;

위의 경우와 아래의 경우는 같은 대입 연산자이지만 메커니즘은 완전 다르다.

위의 경우는 생성자의 호출이라는 메커니즘을 통해 obj1이 obj2에 복사되고
아래의 경우는 대입 연산자 오버로딩이라는 개념으로 복사된다.

c++에서는 객체 간의 대입이라는 것을 기본 자료형의 대입과는 다른 시각으로 바라본다. 즉, 대입연산자가 있는데 양쪽의 피연산자들이 기본 자료형이 아니라면 컴파일러는 operator=()을 호출하는 것이다.

하지만 누군가는 의아할 것이다. 한 번도 이런 함수를 정의한 적이 없는데 객체간 대입은 잘써왔기 때문이다.
이것이 가능했던 것은 디폴트 생성자처럼 디폴트 대입 연산자라는 것이 존재하기 때문이다.

디폴트 대입 연산자

디폴트 대입 연산자는 객체의 멤버와 멤버 간의 복사를 일으킨다.
한마디로 말하자면 디폴트 복사 생성자와 별 다를 바가 없다.

그 말은 디폴트 복사 생성자의 문제점을 그대로 안고 간다는 것이다.
사실 디폴트 복사 생성자보다 해결해야할 문제가 조금 더 많다.

  1. 얕은 복사의 문제점
    디폴트 복사 생성자에서도 그러했듯 단순히 멤버와 멤버 간의 복사가 이루어진다.
    그 결과 포인터에 저장된 값도 단순 복사가 일어나게 되고 이중 소멸 등의 문제를 야기한다.
  1. 메모리 leakage
    이 문제는 obj2가 이미 생성 완료된 객체라는 점에서 비롯된다.
    이미 생성 완료된 객체라는 것은 생성자가 실행이 끝났다는 뜻이고 만일 생성자에서 동적 할당을 했다면, 디폴트 대입 연산자가 실행될때 동적 할당된 메모리가 유실된다.

위의 문제들은 모두 포인터 형과 관계가 있다. 만일 멤버 변수 중에 포인터가 있다면 대입 연산자의 정의에 신경을 써야 한다.

sub class의 대입 연산자

상속관계를 전제로 해보자.
sub class는 super class의 property를 그대로 상속한다.
이 특징과 대입 연산을 연결해보면, 상식적으로 대입연산이 이뤄지면 sub class의 객체의 모든 멤버 변수들이 복사 되어야 한다.
그렇다고 한다면 기초 클래스의 속성은 어떤 대입연산자를 통해 복사되어야 마땅하겠는가?

우린 이미 생성자와 관련해서 이런 고민을 해본적 있다.
원칙은 하나다. 기초 클래스의 속성이므로 기초 클래스에 대입 연산자가 정의되어 있어야할 것이다.

디폴트 대입 연산자의 경우 유도 클래스의 대입 연산자와 기초 클래스의 대입 연산자가 다 실행되도록 한다.
하지만 우리가 대입 연산자를 직접 정의한 경우, 유도 클래스의 대입 연산자에서 기초 클래스의 대입 연산자를 호출해야 한다.


인덱스 연산자

자료구조 같은 것을 구현하는 데 있어 살면서 우리는 종종 기본 배열의 몇가지 특성이 마음에 들지 않을 수 있다.
이런 경우 우리가 직접 배열을 구현할 수 있으면 좋을 것이다.
이를 테면 이렇게 말이다.

NumArray numArrObj1(30);
numArrObj1[3] = 10;//기존의 인덱스 연산자와는 의미적으로 같은 기능을 수행하지만 문법적으로는 다른 연산자이다.

이런 식으로 기존과 동일한 문법을 사용하지만 요구사항에는 더 부합하는 그런 배열을 구현하는 것 말이다.
이런 것을 클래스를 통해 할 수 있고 연산자 오버로딩을 하면 위의 코드에서처럼 인덱싱도 할 수 있다.

인덱스 연산자의 오버로딩 방법은 멤버함수로만 정의할 수 있다는 점을 제외하면 다른 것들과 다를 바 없다.

인덱스 연산자를 통해 새로운 배열을 정의하는 것은 몇가지 주의할 점이 있지만 간단하게 어떤 부분인지 소개만 하겠다.

  1. 배열의 참조와 값 저장하는 함수 따로 정의하기
    여기서 참조는 값의 변경을 허용하지 않는다는 것이고 저장은 값을 변경하겠다는 의미이다.
    이는 반환형에 차이가 있다. 예를 들어 operator[] 함수의 반환형이 int면 외부에서 저장된 값에 접근불가하다.
    헌제 int&로 반환하면 값의 변경이 가능해진다.
    문제는 반환형으로 함수의 오버로딩이 되지 않는다는 것이다.
    이때는 const로 오버로딩하는 것이 좋은 해결책이 된다.

  2. 배열 전체 복사의 허용X
    배열은 안전성을 목적으로 전체 복사를 허용하고 있지 않다.
    그 원칙은 우리가 배열을 정의할 때도 지키는 것이 좋다.
    이럴 때는 대입 연산이 이뤄지는 대입 연산자와 복사 생성자를 외부 함수에서 호출할 수 없도록 private으로 선언하는 것이 좋은 방법이 될 수 있다.

좋은 웹페이지 즐겨찾기