C++ 3법칙(Rule of Three)이란?
3법칙은 C++ 프로그래밍에서 널리 통용되는 경험 법칙이자 좋은 코딩 습관입니다. 이 규칙에 따르면, 클래스에서 다음 중 하나라도 명시적으로 정의해야 한다면 나머지 두 가지도 함께 정의할 필요가 있을 가능성이 높습니다.
- 복사 생성자(copy constructor)
- 대입 연산자(assignment operator)
- 소멸자(destructor)
그 이유는 무엇일까요? 위 세 가지 중 하나라도 필요하다는 것은 해당 클래스가 동적으로 할당된 자원을 직접 관리하고 있다는 신호이기 때문입니다. 예를 들어 대입 연산자가 필요한 상황이라면 객체를 참조로 전달받아 복사본을 만들고 있고, 그 과정에서 새로운 자원을 할당하게 됩니다. 이렇게 할당된 자원을 안전하게 복사하려면 복사 생성자가, 사용이 끝난 자원을 해제하려면 소멸자가 반드시 필요합니다.
C++11 이후의 표준: 5법칙(Rule of Five)
5법칙은 C++11에서 이동 의미론(move semantics)이 도입되면서 3법칙을 확장한 개념입니다. 마찬가지로 C++에서 자원을 관리할 때 적용되며, 메모리 누수 등 흔히 발생하는 문제를 예방하는 데 큰 도움이 됩니다.
5법칙의 핵심은 "다섯 가지 특수 멤버 함수 중 하나라도 작성해야 한다면, 나머지에 대한 정책(policy)도 함께 수립해야 한다"는 것입니다. 예를 들어 Foo라는 객체를 관리하는 FooManager를 구현한다고 가정하면, 아래 다섯 가지 함수를 모두 고려해야 합니다.
1. 소멸자(Destructor)
관리 객체가 스코프(scope)를 벗어날 때, 자신이 관리하던 모든 자원을 해제해야 합니다.
2. 대입 연산자(Assignment Operator)
직접 제공하지 않으면 컴파일러가 기본 대입 연산자를 생성합니다. 기본 대입 연산자는 멤버 단위 복사, 즉 얕은 복사(shallow copy)만 수행합니다. 깊은 복사(deep copy)가 이루어지지 않기 때문에 메모리 누수나 잘못된 대입 같은 문제가 발생할 수 있습니다.
3. 복사 생성자(Copy Constructor)
컴파일러가 제공하는 기본 복사 생성자 역시 모든 멤버를 단순 복사합니다. 따라서 대입 연산자와 마찬가지로 얕은 복사로 인한 문제가 발생할 수 있습니다.
4. 이동 생성자(Move Constructor)
객체 복사는 임시 객체를 생성하고, 복사하고, 다시 파괴하는 과정을 거치므로 비용이 클 수 있습니다. C++11은 r-value 참조(r-value reference) 개념을 도입해 이 문제를 해결했습니다. r-value는 이름 없는 객체, 즉 임시 객체를 의미하며, r-value 참조는 이러한 임시 객체에 명시적으로 바인딩될 수 있습니다. 이동 생성자는 r-value 참조를 활용해 전달된 임시 객체의 자원을 효율적으로 가져옵니다.
5. 이동 대입 연산자(Move Assignment Operator)
하나의 자원을 한 번에 한 곳에서만 소유하는 것이 유용한 경우가 많습니다. 이때 자원의 소유권을 한 관리 객체에서 다른 객체로 이전할 수 있는데, 이런 상황에서 이동 대입 연산자가 유용하게 사용됩니다.
마무리
정리하면, 3법칙은 C++98 시절의 자원 관리 원칙이고, 5법칙은 C++11의 이동 의미론을 반영한 확장판입니다. 최신 C++에서는 이 두 규칙을 모두 이해하고 상황에 맞게 적용하는 것이 안전하고 효율적인 코드를 작성하는 데 필수적입니다. 5법칙에 대해 더 깊이 학습하고 싶다면 Feabhas에서 제공하는 Rule of the Big Five 문서(PDF)를 참고하는 것도 좋은 방법입니다.