C++ 복사 생성자와 대입 연산자 뜯어보기: &, const, operator

Bean·2026년 6월 21일

프로그래밍

목록 보기
48/49

0. 들어가며: 싱글턴 코드 속 낯선 키워드들

지난 포스팅에서 객체의 단일성을 보장하는 싱글턴(Singleton) 패턴을 구현하면서, 복사와 대입을 막기 위해 아래와 같은 코드를 작성해 보았습니다.

private:
    MySingleton(const MySingleton&) = delete;            // 복사 생성 금지
    MySingleton& operator=(const MySingleton&) = delete; // 복사 대입 금지

싱글턴을 의도대로 동작하게 만드는 고마운 코드이지만, C++ 입문자분들에게는 = delete 뒤에 붙은 &, const, operator 같은 키워드들이 한눈에 들어오지 않고 헷갈릴 수 있습니다.

오늘은 이 세 가지 핵심 요소가 왜 필요하고, 내부적으로 어떤 역할을 하는지 C++의 복사 메커니즘과 연산자 오버로딩 규칙을 바탕으로 알기 쉽게 풀어보겠습니다!


1. 왜 포인터(*)가 아니라 참조(&)를 쓸까? (무한 루프의 늪)

C++에서 복사 생성자는 "나와 똑같은 타입의 객체를 원본 삼아(입력받아) 나를 만드는 생성자"로 정의됩니다.

MySingleton(const MySingleton& other);

여기서 왜 포인터(*)가 아니라 굳이 참조(&)를 써야 할까요? 만약 &를 빼고 일반 변수 형태로 인자를 받도록 코드를 짜면 엄청난 모순이 생깁니다.

  • 값에 의한 전달(Pass by Value): C++에서 함수 인자로 객체를 그대로 넘기면, 함수 내부에서 사용할 새로운 복사본이 만들어집니다.
  • 무한 루프의 늪: 그런데 복사본을 만들려면 뭐가 필요하죠? 바로 복사 생성자가 호출되어야 합니다. 복사 생성자를 호출하려고 인자를 넘기니 또 복사본을 만들어야 하고, 그래서 복사 생성자가 또 호출되는 상황이 벌어집니다.

결론: &를 붙이지 않으면 복사 생성자를 실행하기 위해 복사 생성자를 무한히 반복 호출하는 늪에 빠집니다. 따라서 이를 원천 차단하기 위해 원본의 메모리 주소만 안전하게 가리키는 참조(&) 형태로 문법이 고정된 것입니다.


2. 매개변수 앞의 const는 무슨 역할일까?

& 앞에 붙은 const복사할 원본 데이터를 보호하기 위한 안전장치입니다.

우리가 a = b;라는 코드를 작성할 때, 기대하는 행동은 "b(원본)의 내용을 읽어서 a에 그대로 복사하는 것"입니다. 복사하는 도중에 원본 데이터인 b가 수정되면 절대 안 되겠죠?

const를 붙여주면 컴파일러에게 다음과 같이 선언하는 것과 같습니다.

"이 함수 안에서는 입력받은 원본(b)을 읽기만 할 뿐, 절대 1비트도 수정하지 않겠다!"

만약 개발자가 복사 함수 내부에서 실수로 원본 데이터를 수정하는 코드를 작성하면, 컴파일러가 알아서 에러를 내뿜으며 빌드를 가로막아 줍니다. 안전한 코딩을 위한 필수 규칙인 셈입니다.


3. operator= 이란 무엇인가요? (연산자 오버로딩)

이 키워드는 C++의 가장 강력한 무기 중 하나인 연산자 오버로딩(Operator Overloading) 문법입니다.
수학 연산자 기호인 +, -, =, == 같은 것들을 일반 숫자(int, float)뿐만 아니라, 우리가 직접 만든 클래스(객체)끼리도 쓸 수 있도록 기호의 기능을 재정의하는 것입니다.

MySingleton& operator=( ... );

operator=는 말 그대로 "이 클래스의 객체끼리 이퀄(=) 기호를 쓸 때 호출되는 함수를 내가 정의하겠다"라는 의미입니다.

코드에 a = b;라고 적으면, 컴파일러는 내부적으로 a.operator=(b);라는 멤버 함수를 알아서 호출해 줍니다. 대입 기호의 동작을 사용자 정의 클래스 입맛에 맞게 바꿀 수 있는 것이죠.


4. 다른 연산자도 오버로딩이 가능한가요?

네, 당연히 가능합니다! 대입 연산자뿐만 아니라 사칙연산(+, -), 비교 연산(==, !=) 등 거의 모든 기호를 재정의할 수 있습니다.

이해를 돕기 위해 3차원 공간의 좌표를 나타내는 Vector3D 클래스를 예로 들어볼까요? 원래 C++에서는 기본적으로 클래스끼리 더하려고 하면 컴파일러가 에러를 냅니다. 하지만 operator+를 정의해 주면 이야기가 달라집니다.

class Vector3D {
public:
    float x, y, z;

    // 더하기(+) 기호의 의미를 새롭게 재정의
    Vector3D operator+(const Vector3D& other) {
        // 내 좌표와 상대방(other)의 좌표를 각각 더해서 새로운 벡터 객체 반환
        return Vector3D{ this->x + other.x, this->y + other.y, this->z + other.z };
    }
};

이렇게 정의해 두면 실제 메인 코드에서 마치 일반 숫자를 더하듯이 직관적이고 직관적인 코드를 작성할 수 있습니다.

Vector3D v1{1.0f, 2.0f, 3.0f};
Vector3D v2{4.0f, 5.0f, 6.0f};

// 컴파일러는 내부적으로 v1.operator+(v2) 형태로 바꾸어 실행합니다!
Vector3D v3 = v1 + v2; 

💡 C++에서 재정의 가능한 연산자 종류

  • 산술 연산자: +, -, *, /, %
  • 비교 연산자: ==, !=, <, >
  • 입출력 연산자: <<, >>
  • 포인터 관련 연산자: ->, *

5. 최종 요약: 싱글턴 코드가 막은 것들

자, 이제 배운 개념들을 모두 조합해서 처음에 보았던 싱글턴 코드를 다시 읽어봅시다. 처음에 봤을 때와는 전혀 다르게 보일 것입니다.

// 1. 나랑 똑같은 타입의 원본(&)을 안전하게(const) 읽어서 '복사 생성'하는 행위를 금지(=delete)
MySingleton(const MySingleton&) = delete;

// 2. 이미 만들어진 객체끼리 이퀄 기호(operator=)를 써서 '대입 복사'하는 행위를 금지(=delete)
MySingleton& operator=(const MySingleton&) = delete;
  • operator=: "이 클래스로 만든 객체끼리 대입(=)하려고 할 때 호출되는 함수인데..."
  • = delete;: "그 기능을 아예 삭제해서 세상에서 없애버리겠다!"

결과적으로 누군가 코드에서 singleton1 = singleton2;라고 쓰는 순간, 컴파일러가 이를 딱 가로막고 에러를 내뿜게 됩니다.

&const는 복사를 가장 안전하고 효율적으로 처리하기 위한 C++ 표준 문법의 규칙이며, operator=기호의 동작 방식을 뜻합니다. 이 완벽한 복사 세트를 = delete로 묶어둠으로써 "이 클래스는 어떤 방식으로든 복제본을 만들 수 없다"를 컴파일 단계에서 강제할 수 있는 것이죠. C++이 제공하는 아주 강력하고 매력적인 제어 방식 방식 중 하나입니다.

오늘 내용이 C++의 깊은 문법을 이해하는 데 도움이 되셨기를 바라며, 다음에도 유익한 개발 팁으로 찾아오겠습니다. 궁금한 점은 댓글로 남겨주세요! 😉

profile
AI developer

0개의 댓글