C++/C++ 복사 생성자, 복사 할당 연산자(Canonical form)

복사 할당 연산자

재윤 2023. 12. 1. 12:56
반응형

대입 연산자 오버로딩

→ C++의 한가지 멋진 기능 중 하나는 클래스를 선언할 때 연산자에 특별한 의미를 부여할 수 있다는 것.

  • 이것을 operator overloading이라고 함.
  • 아래 나열되는 특징 네이밍 컨벤션을 따라 클래스에 멤버함수를 선언함으로서 C++ operator overload를 구현 가능.
  • 예를 들어 ‘+’연산자를 오버로딩하기 위해서 operator+ 라는 이름의 멤버함수를 만들어주면 됨.

일반적으로 오버로딩에 많이 쓰이는 연산자는 다음과 같다.

  • = (대입 연산자)
  • +, - , * (이진 산술 연산자)
  • +=, =, = (복합 대입 연산자)
  • ==, != (비교 연산자)

대입 연산자 =

대입 연산자 구현

class MyClass {
public:
	...
    MyClass & operator=(const MyClass &rhs);
    ...
}

MyClass a, b;
...
b = a;  // b.operator=(a); 와 같은 의미

= 연산자는 대입의 우항에 상수 참조를 받는 것을 확인 가능하다.

  • 우항은 변경되지 않고 오직 좌항만 변경하는 것을 보장하도록 구현하기 위함.
  • 연산자 반환은 참조로 하는 것을 확인할 수 있다.
  • 다음은 operator chaining을 허용하기 위함.
int a, b, c, d, e;
a = b = c = d = e = 42;

이는 컴파일러에 의해 다음과 같이 해석된다.

a = (b = (c = (d = (e = 42))));
  • 다르게 말해서, 대입(할당)은 우측결합이다.
  • 마지막 대입 연산을 먼저 계산하고, 좌측으로 연산하며 값이 전달되는 방식.
  • 이 operator chaining을 지원하기 위해 대입 연산자는 반드시 어떤 값을 반환해야 한다.
  • 반드시 반환되어야 하는 값은 좌항의 참조.
  • 여기서 반환하는 참조는 const로 선언되어서는 안 된다. 왜냐하면 다음과 같은 코드도 작성 가능
MyClass a, b, c;
...
(a = b) = c; // What??

가상의 myClass의 대입 연산자를 다음과 같이 구현 가능

// Take a const-reference to the right-hand side of the assignment.
  // Return a non-const reference to the left-hand side.
  MyClass& MyClass::operator=(const MyClass &rhs) {
    ...  // Do the assignment operation!

    return *this;  // Return a reference to myself.
  }

this는 멤버함수가 호출되고 있는 오브젝트의 포인터이다. a = b는 a.operator=(b)로 간주할 수 있다. 따라서 참조자를 반환해야 하므로 *this를 반환한다.

자기 할당 체크

→ 새로 정의하는 클래스 내에서 메모리할당을 한다면 중요한 것을 기억해야 한다. 일반적으로 대입연산자 함수 내에서는 다음과 같은 흐름으로 동작.

MyClass& MyClass::operator=(const MyClass &rhs) {
    // 1.  MyClass 내에서 사용중인 메모리를 해제하고 
    // 2.  대입해오는 우항의 값을 저장할 메모리를 새롭게 할당한다.
    // 3.  우항의 값을 새로 할당한 메모리 공간에 복사한다.
    // 4.  *this 를 반환한다.
  }

아래와 같은 코드를 작성하면 우째 동작할까?

MyClass mc;
...
mc = mc;  // 헉
  • 이 코드가 실행되는 순간 대혼란이 발생할 것임.
    • 왜? mc는 좌항이면서 우항이므로 좌항에서 메모리를 해제한 순간 복사할 원본의 메모리도 해제가 된다는 것임.
    • 따라서 어떤 값이 복사될지 알 수가 없음!!
  • 자기 할당을 체크하는 방법을 알아보자
  • 두 인스턴스가 완전히 동일한가 체크할 필요없이 두 객체의 주소가 같은지 보면 됨.
  • 주소가 같다면 대입 x
MyClass& MyClass::operator=(const MyClass &rhs) {
    // Check for self-assignment!
    if (this == &rhs)      // Same object?
      return *this;        // Yes, so skip assignment, and just return *this.

    ... // Deallocate, allocate new space, copy values...

    return *this;
  }

정리해보자.

  1. 매개변수(우항)로 상수 참조를 취하라.
  2. operator chaining을 지원하기 위해 좌항의 참조를 반환하라. (*this)
  3. 포인터를 비교하여 자기할당을 체크하라. (this vs &rhs)

 

위 글은 이 분의 글을 보고 공부하였습니다.

C++ 대입 연산자 오버로딩 가이드

 

C++ 대입 연산자 오버로딩 가이드

C++의 한가지 멋진 기능 중 하나는 클래스를 선언할 때 연산자에 특별한 의미를 부여할 수 있다는 것이다. 이것은 operator overloading이라고 부른다. 아래 나열되는 특정 네이밍 컨벤션을 따라 클래

velog.io

 

반응형