프로그래밍 언어/Effective C++

[Effective C++]항목 38~40: 객체 지향 설계 3

우향우@ 2023. 6. 1. 23:49

항목 38 : 객체 합성

객체 합성은 그냥 한 클래스가 다른 클래스를 멤버로 가지는 것을 의미한다.

이는 개념적으로 보통 두가지 중 하나의 의미를 가진다.

has-a

밖 클래스가 안 클래스를 개념적으로 소유한 경우이다. person 클래스가 string name, Address adress등의 멤버를 가진 경우에 해당된다. 보통 클래스가 현실의 객체를 나타낼때 나타난다..

is-implemented-in-terms-of

밖 클래스가 안 클래스를 구현에 활용하는 경우이다. 하나의 부품으로 사용했다고 봐도 무방할 듯 하다. stack 클래스가 내부적으로 list 객체를 멤버로 가지고 있고 이를 이용하여 구현된 경우 등이 해당된다. 보통 시스템 구현을 위한 인공물 클래스에서 나타난다.

 

항목 39 : private 상속 

private상속은 다음 두가지 기능을 가진다.

1. private로 부모를 상속받은 자식은 부모로 암시적 형변환이 되지않는다. 즉 개념적으로 is-a 관계가 아니다.

2. private로 부모를 상속받으면 부모의 protected, public 멤버들이 자기 scope에서 private로 선언된 것 처럼 작동하게 된다. 즉 자신의 클래스 내부에서는 접근 가능하지만 private로 선언됬기에 외부에서 접근 할 수 없게된다. 즉 부모의 인터페이스를 상속받지 않게되는 셈이다.

 

private 상속은 개념적으로 다음 의미를 가진다.

is-implemented-in-terms-of

위 객체 합성에서의 것과 동일하며 부모 클래스의 구현만을 가져와서 자신의 구현에 응용하는 경우이다. 이때 is-a는 성립하지 않는다.

 

그럼 is-implemented-in-terms-of 개념을 디자인할 때 private 상속과 객체 합성의 장점은 다음과 같다. 여기서 밖 클래스와 부모 클래스는 대상 클래스라는 단어로 통일한다.

 

private 상속

대상 클래스의 protected에도 접근가능하다.

대상 클래스의 가상 함수를 오버라이드하기 편리하다.

대상 클래스가 멤버 변수가 전혀없을때 패딩 메모리를 할당하지 않을 수도 있다.

 

객체 합성

전반적으로 자주 사용되는 디자인이며 이해하기 쉽다.

대상 클래스의 가상 함수를 오버라이드한 내부 클래스를 하나 만들고 이를 멤버로 가지게 하여 가상 함수를 오버라이드 한 것처럼 사용할 수 있다. 이때 클래스 자신을 상속받은 자식 클래스에서 이 가상함수의 구현을 바꿀 수 없게 제한할 수 있다.

대상 클래스 포인터만 멤버로 가지게 하면 컴파일 의존성을 낮출 수 있다.

 

위 사항들을 고려하여 현재 상황에 맞는 적절한 디자인을 선택하면 된다. 다만 대부분의 경우 객체 합성을 사용하는 분위기이기에 가독성이나 유지보수성을 고려했을땐 웬만해선 객체 합성을 사용하는 것이 좋다.

 

항목 40 : 다중 상속 

다중 상속은 여러 클래스를 상속받는 것을 의미한다. 다중 상속은 잘 사용하면 좋은 디자인이 나올 수 있지만 잘못하면 프로그램 구조가 겉잡을 수 없게 복잡해지므로 굉장히 신중히 사용해야한다.

virtual 상속

만약 최상위 클래스 A의 자식 B와 C가 있고 B와 C를 다중상속하는 D라는 클래스가 있다고 하자. 이때 D는 B,C 각각을 통해 A를 두번 상속받게 된다. 이때 C++에서는 기본적으로 D가 A의 멤버 변수를 두개씩 가지게 만들어 버린다. 이를 막기 위해서는 B와 C가 A를 virtual 키워드를 붙여 상속해야한다. 이렇게 하면 다중상속에서 중복 소유 문제를 막을 수 있다. 하지만 virtual 상속은 객체 메모리 크기나 virtual 부모 멤버에 대한 접근 속도에 큰 오버헤드를 발생시킨다. 추가로 virtual 조상을 가지는 클래스들은 해당 조상의 멤버들을 초기화할 때 따로 신경써서 해주어야한다는 제약이 있으므로 생산성에도 큰 오버헤드가 발생한다. 따라서 꼭 필요한 경우가 아니라면 virtual 상속은 피해야한다. 꼭 써야한다면 최대한 멤버 변수는 가지지 않도록 하는 것이 좋다. 기능적으로 완전히 대응되지는 않지만 굳이 대응시킨다면 자바나 닷넷의 interface에 가장 가까운 기능에 해당한다. 다만 멤버 변수 중복문제만 없다면 virtual이 아닌 일반 상속으로도 당연히 interface의 기능을 하는 계층을 구현가능하다.

 

다중 상속을 깔끔하게 사용할 수 있는 한 예시는 is-a 관계로써 인터페이스와 구현을 모두 상속받는 부모 클래스를 public으로 상속받고, is-implemented-in-terms-of 관계로써 구현만을 가져와 사용하는 클래스를 private로 상속하여 사용하는 경우가 있다. 이러면 is-a 계층이 복잡해지지도 않으며 생산성도 좋기에 유용한 디자인이 될 수 있다.

 

마지막으로 앞에서 말했듯이 다중 상속은 잘못하면 프로그램 구조가 복잡해질 수 있기에 피할 수 있다면 피하는 것이 좋다. 하지만 현재 상황에서 다중 상속이 너무나 좋고 명료하다고 판단이 된다면 그럴때만 과감히 사용하면 좋은 결과를 낼 수 있을 것이다.

 

 

 

 

 

 

참고서적

Effective C++