C++ 프로그래밍에서 객체를 결합하는 두 가지 방법인 집합과 구성을 자주 사용합니다. 이 개념들은 클래스 간의 서로 다른 관계를 반영하며, 관련 객체의 생명 주기와 파괴 책임에 영향을 미칩니다.
질문 역사:
객체 지향 설계에서 객체 간의 의존성을 구분하는 것은 항상 중요했습니다. 객체 지향 언어(Smalltalk, C++, Java)가 등장하면서 "부분 - 전체" 관계를 모델링하는 방법에 대한 질문이 생겼습니다. C++에서는 수동 메모리 관리와 객체의 생명 주기 때문에 이는 특히 중요해졌습니다.
문제:
집합과 구성 간의 잘못된 선택은 메모리 누수, 리소스 중복, 객체 파괴 오류를 초래합니다. 또한 이 개념들이 혼동되는 경우가 종종 있습니다.
해결책:
코드 예시:
// 구성: class Engine {}; class Car { Engine engine; // Engine은 Car과 함께 생성되고 파괴됨 }; // 집합: class Person {}; class Team { std::vector<Person*> members; // Person 객체를 가리키며 소유하지 않음 };
주요 특징:
클래스 멤버에 객체에 대한 포인터가 있으면 항상 집합인가?
아니요! 만약 클래스가 이 포인터를 소유하고 있다면(예: std::unique_ptr를 통해), 여전히 구성입니다. 연결 유형은 필드의 유형이 아니라 생명 주기에 대한 책임에 따라 결정됩니다.
class House { std::unique_ptr<Room> room; // 구성, House는 Room을 소유함 };
구성이 참조 또는 raw 포인터를 통해 구현될 수 있는가?
가능합니다 — 그러나 단지 소유자가 생성하고 파괴하는 경우에만, 링크 또는 포인터가 최적화를 위해 사용되는 경우에 가능합니다. 그러나 소유권을 명확히 표현하기 위해 값이나 스마트 포인터를 사용하는 것이 훨씬 좋습니다.
구성에서 파트 객체가 소유자 외부에서 생성되어 전달된다면 어떻게 될까?
이 경우 구성의 불변성이 깨질 위험이 발생합니다: 외부에서 생성된 객체가 소유자에게 전달되면 소유자가 삭제하고 다른 곳에 링크가 남아있다면, dangling pointer가 발생할 수 있습니다. 프로젝트에서 소유권 및 파괴 책임을 엄격하게 정의해야 합니다.
한 팀은 모든 내부 객체를 raw 포인터로 컨테이너에 저장하고 소멸자에서 수동으로 삭제하기로 결정했습니다. 모든 것이 잘 작동하다가 소유권 체계가 변경되었습니다. 결과적으로 포인터가 두 번 해제되어 충돌이 발생했습니다.
장점:
단점:
다른 팀은 실제 소유 관계에 대해 std::unique_ptr로 전환하고, 비소유는 임시 참조 형태로만 사용했습니다. 이는 아키텍처를 명확하게 표현했습니다.
장점:
단점: