프로그래밍 언어/C & C++ 정리

대입 연산자

뽀또치즈맛 2025. 3. 4. 21:14

대입 연산자 정의 방법


대입 연산자를 구현하는 방법은
복제 생성자와 비슷하지만,
몇 가지 중요한 차이점이 있다.

첫째, 복제 생성자는 초기화할 때 단 한 번만 호출된다.

이때 대상의 객체는 유효한 값을 가지고 있지 않다.
또한 대입 연산자는 객체에 이미 할당된 값을 덮어쓸 수 있다.

그러므로 객체에서 메모리를 동적할당하지 않는 한
이 차이점은 크게 드러나지 않는다.

이에 대한 코드 예시는 아래와 같다.

MyClass myClass;
myClass = myClass;


대입 연산자를 구현할 때는
자기 자신을 대입하는 경우도 고려해야 하지만,

고려하지 않아도 될 경우가 있다.
1) 클래스 내부에 멤버가 하나고
2) 이 멤버가 기본형이라면
이를 고려하지 않아도 된다.
위 예제의 MyClass가 double형 하나만 가진 클래스라면
이를 성립하는 것이다.

달리 말하면
1) 클래스 내부 멤버가 하나가 아니거나,
2) 멤버가 하나더라도 기본형이 아니라면
대입 연산자를 구현할 때
자기 자신을 대입하는 경우를 고려해야한다.

이에 대한 코드는 다음과 같다.

MyClass& MyClass::operator=(const MyClass &ref)
{
   if(this == ref) {
      return *this;
   }
   
   mVal = ref.mVal;
   return *this;
}


첫 번째 줄은 자기 자신을 대입하는지 확인한다.
그런데 코드가 좀 복잡하다.
자기 자신을 대입하는 동작은
완쪽과 오른쪽이 같을 때 성립한다.

두 객체가 서로 같은지 알아내는 방법 중
하나는 서로 똑같은 메모리 공간에 있는지 확인하는 것이다.
좀 더 구체적으로 표현하면
두 객체에 대한 포인터가 똑같은지 알아보면 된다.

this라는 포인터는
현재 객체에서 호출할 수 있는
모든 메서드에 접근할 수 있다.

그러므로 this를 왼쪽 객체로 지정한 것이다.

마찬가지로 &ref는
오른쪽 객체를 가리키는 포인터이다.
두 포인터의 값이 같으면
자기 자신을 대입한다고 볼 수 있다.

그런데 리턴 타입이 MyClass& 이기 때문에
값을 정확히 리턴해야한다.
따라서 대입 연산자는 항상 this*를 리턴한다.

자기 자신을 리턴할 때도 마찬가지이다.

this는 현재 메서드가 속한 객체를 가리키는 포인터이다.
따라서 this*란,
해당 객체를 가리키는 것이다.

++Plus++
객체와 인스턴스는 일반적으로 같은 개념을 가리키지만,
객체가 생성된 상태를 구체적으로 나타내는 경우에는
인스턴스라는 용어를 사용한다.
++++

이러한 this*를 사용하게 되면
컴파일러는 선언된 리턴값과 일치하는
객체에 대한 레퍼런스(=포인터)를 리턴한다.

반면 자기 대입이 아닌 경우
if문 바깥의 코드처럼 직접
모든 멤버에 대해 일일히 대입 연산을 수행해야 한다.

명시적으로 디폴트로 만들거나
삭제한 대입연산자

MyClass& operator=(const MyClass& ref) = defualt
MyClass& operator=(const MyClass& ref) = delete


컴파일러가 자동으로 만들어주는 대입 연산자를
위와 같이 명시적으로 디폴트로 만들어주거나 삭제할 수 있다.


복제와 대입 구분하기


때로는 대입 연산자로 대입하지 않고
복제 생성자로 초기화하는 경우를 판단하기 힘든 경우가 있다.

기본적으로
선언에 가까우면 복제(복사) 생성자,
대입에 가까우면 대입 연산자를 사용한다.

리턴 값이 객체인 경우

함수나 메서드의 리턴 값이 객체인 경우
복제(복사)나 대입 중 어느 방식이 적용되는지
판단하기 힘들 때가 있다.

다음과 같은 코드를 예시로 들어보자

string MyClass::GetString()
{
   return doibleToString(mVal);
}

그리고 다음과 위 메서드를
다음과 같이 호출하는 상황을
리턴 값이 객체인 경우의 예시로 들겠다.

MyClass myClass {5};

string s2 = myClass.GetString();


GetString 함수가 스트링을 리턴할 때
컴파일러는 string의 복제 생성자를 호출해서
이름 없는 임시 string 객체를 생성한다.

이 객체를 s2에 대입하면
s2의 대입 연산자가 호출되는데
방금 만든 string 임시 객체를
이 연산자의 매개변수로 전달한다.

그런 다음 임시로 생성한 string 객체를 삭제한다.

따라서

string s2 = myClass.GetString()
이 한줄의 코드에

서로 다른 두 객체에 대하여
복제(복사)생성자와 대입 연산자 모두 호출된다.

하지만 이는 컴파일러마다 얼마든지 달리 처리할 수 있으며,
값을 리턴할 때 복제 생성자의 오버헤드가 크다면
복제 생략(=이동 개념)을 적용해서 최적화하기도 한다.

'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글

생성자  (0) 2025.02.19
동적 메모리 다루기  (2) 2025.02.04
그래서 다형성이 뭐라고요?  (0) 2025.01.12
인라인 함수  (15) 2024.12.29
인터페이스를 사용하기 쉽게 설계하기 (2)  (0) 2024.12.05