52
Effective C++ 정정 Chap5~6 131054 정정정

Chapter5 ~ 6

Embed Size (px)

Citation preview

Effective C++ 정리Chap5~6

131054 이인재

항목 26 : 변수 정의는 늦출 수 있는 데까지 늦추자

• 생성자 혹은 소멸자를 끌고 다니는 타입으로 변수를 정의하면 반드시 생성자 호출 , 소멸자 호출을 하는데 들어가는 비용이 생긴다 .

• 변수가 정의되었는데 상요되지 않을 경우에도 쓸데없는 비용이 생긴다 .

• -> 사용될 줄 알고 생성한 변수가 사용이 안되는 경우가 생길 수 있다 .

• 언뜻 보면 encrypted 가 반드시 사용되는 변수인 것 같지만 if 문에서 걸리면 사용되지 않는 변수가 된다 .

변수 정의는 꼭 필요해질 때 하자

• 조건에 걸려버리면 encrypted는 쓸모없는 변수가 되어버리므로 , 꼭 필요해지기 전까지 encrypted 의 정의는 미루는 것이 좋다 . • encrypted 가 사용 될 수 있는 시점에 와야 비로소

변수로서 의미를 가지므로 다음과 같이 선언하면 변수 낭비의 비용이 발생하지 않는다 .

• 주의할 점은 루프안에서의 문제인데 이때는 A : 루프 바깥족에 정의 -> 생성자 1 + 소멸자 1 + 대입 nB : 루프 안쪽에서 정의 -> 생성자 n + 소멸자 n두 방법의 효율을 따져서 정한다 .

정의와 동시와 초기화를 하는게 좋다

• 초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지 보아야 한다 .-> 대입연산자를 이용하면 초기화를 이용하는 것보다 비용이 더 들어가기 때문에 , 정의와 동시에 초기화를 하면 기본 생성자 호출의 낭비를 막을 수 있다 .

• 루프안에서는 일반적으로는 루프 바깥족에서의 정의하는 것이 효율이 좋다 ( 대입에 들어가는 비용이 생성자 - 소멸자 쌍보다 더 적을 경우가 많다 )

항목 26 정리

• 변수 정의는 늦출 수 있는 때까지 늦추자

• 초기화 인자가 나올때까지 , 객체 정의를 늦추자

• 루프안에서의 변수 정의는 케이스 바이 케이스로 효율을 따져서 결정하자

항목 27 : 캐스팅은 절약 , 또 절약

• 캐스트에 항상 주의해야 한다 .

• C 스타일의 캐스트 , C++스타일의 캐스트 중 C++ 스타일의 캐스트를 쓰는 것이 더 좋다-> 어떤 것인지 알아보기 쉽고 , 캐스트의 사용목적을 좀더 명확하게 알아 볼 수 있다 .

• c 스타일의 캐스트와 , c++ 스타일의 static_cast를 통해 double 타입의 a 를 int 타입으로 강제 형변환 시켜준 예시

C 와 C++ 에서의 캐스팅 문법

•될 수 있으면 C++ 의 캐스팅 방법을 쓰는 것이 좋다 !

실수하기 쉬운 캐스팅

• 가상 함수를 파생 클래스에서 재정의해서 구현할 때 기본 클래스의 버전을 호출을 먼저 할 때-> static_cast 를 이용한 this 객체는 임시객체이므로 의도한대로 함수호출이 되지 않는다 .

다운캐스팅 시 주의

•폭포식 dynamic_cast 를 쓰지 말 것 !

다운 캐스팅 없이 파생된 클래스타입의 컨테이너를 이용

다운 캐스팅 없이 가상함수집합으로 기본 클래스에 넣어두기

항목 27 정리• 되도록이면 캐스팅을 피하자

• dynamic_cast 는 정말 신중하게 쓰자 .

• 캐스팅이 반드시 필요하다면 함수 안에 숨길수 있도록 하자

• C 스타일보다 C++ 스타일의 캐스트를 쓰도록 하자

항목 28 : 내부에서 사용하는 객체에 대한 ‘핸들’을 반환하는 코드는 피하자

• 클래스 멤버 데이터는 그 멤버의 참조자를 반환하는 함수들의 접근도에 따라 캡슐화가 정해진다 .

• 참조자 뿐만 아니라 핸들 (참조자 , 포인터 , 반복자 처럼 다른 객체에 손을 댈 수 있게 하는 매개자 ) 을 반환하게 만들면 캡슐화가 보장되지 않는다 .

• 특히 private 로 외부 공개가 금지된 데이터에 대해서 핸들을 반환할 시 그 캡슐화가 깨질 수 있기때문에 주의해야 한다 .

• 핸들을 반환하는 코드는 사용하면 안된다 .

반환타입에 const 를 붙이면 수정 불가하게 만들 수 있다

• const 키워드를 붙임으로써 오직 읽도록만 할 수 있다 .

항목 28 정리

• 어떤 객체의 내부요소에 대한 핸들 ( 참조자 , 포인터 , 반복자 ) 를 반환하는 것은 피하자

• 무효참조 핸들이 생기는 경우를 최소화 하자 .

• 캡슐화 정도를 높이기 위해서 핸들 반환하는 것을 피하자

항목 29 : 예외 안전성이 확보되도록 하자

• 강력한 보장 : 함수 동작 중에 예외가 발생하면 , 프로그램의 상태를 절대로 변경하지 않겠다는 보장 . 이런 함수를 호출하는 것은 원자적인 동작이라 한다 .

• 예외불가 보장 : 예외를 절대로 던지지 않겠다는 보장 . 약속한 동작은 언제나 끝까지 완수하는 함수

• 기본적인 보장 : 함수 동작 중에 예외가 발생하면 , 실행 중인 프로그램에 관련된 모든 것들을 유요한 상태로 유지 . 어떤 객체나 자료구조도 더럽히지 않으며 , 일관성을 유지하게 한다 .

항목 29 정리• 예외 안전성을 갖춘 함수는 실행 중 예외가 발생되더라도

자원을 누출시키지 않는다 .

• 예외 안전성 보장은 기본적인 보장 , 강력한 보장 , 예외 금지 보장이 있다 .

• 강력한 예외 안전성 보장은 ‘복사 - 후 - 맞바꾸기’ 방법을 써서 구현 , 모든 함수에 대해 실용적인 것은 아니다 .

• 어떤 함수가 제공하는 예외 안전성 보장의 강도는 그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 않는다 .

항목 30 : 인라인 함수는 잘 따져서 이해하자

• 인라인 함수는 함수 호출문을 그 함수의 본문으로 바꿔치기

• 함수 호출 비용이 면제

• 메모리가 제한된 프로그램에서는 코드가 커져서 문제가 발생할 수 있음

• inline 은 컴파일러에 대해 ‘요청’ 이지 ‘명령’이 아님

• 암시적인 인라인 방법은 클래스 정의 안에 함수를 바로 구현 ( 정의 )

• 명시적인 방법에는 함수 정의 앞에 inline 키워드를 붙임

• GetAge 멤버함수는 클래스 정의 내부에서 구현되어있고 , 이것은 곧 암시적인 인라인 요청을 하는 것임을 뜻한다 .

• Max 함수라는 것은 명시적으로 inline 키워드를 써서 인라인함수 요청을 컴파일러에게 했다 .

C++ 에서의 인라인 함수• 대부분의 C++ 프로그램에서 함수 인라인은 컴파일 타임에 진행

• 인라인이 끌고 오는 비용은 바로 코드 비대화

• 대부분의 컴파일러의 경우 아무리 인라인 함수로 선언 ( 요청 ) 을 했다 하더라도 , 컴파일러 자신이 보기에 복잡하거나 인라인에 적합하지 않다면 절대로 인라인의 대상에 넣지 않음

• 가상함수는 절대로 인라인 해주지 않음->virtual 의 의미는 어떤 함수를 호출할지 결정하는 작업을 실행중에 한다는 것인데 인라인은 그 위치에 호출된 함수를 끼워넣는 작업이다 . 컴파일러는 실행전에 그 위치에 함수의 코드를 끼워 넣어야 되는데 그 함수가 어떤건지 알 수 없다 .

• 생성자와 소멸자는 인라인하기에 좋지않다 .-> 동적으로 만들었다면 생성자에서 초기화 해주거나 , 상속을 받았다면 기본 클래스의 생성자를 호출해 준다든가 , 멤버 데이터들을 초기화해준다거나 하는 일들을 한다 . 이런것들을 전부 인라인으로 처리해 버리면 호출되는 함수들이 전부 코드로 들어가버리므로 똑같은 함수들을 여러개 가지는 꼴이 되어버릴 수 있다 .

항목 30 정리

• 함수 인라인은 작고 , 자주 호출되는 함수에 대해서만 하자-> 디버깅 및 라이브러리의 업그레이드가 용이 , 프로그램의 속도가 빨라질 가능성이 있음

• 되도록이면 아무것도 인라인하지말고 ( 컴파일러가 알아서 인라인으로 적합한 것은 인라인으로 바꾸어줌 ), 인라인해야하는 경우는 정말 단순한 함수에 한해서만 해줌

항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자

• 어떤 것이 컴파일 되려면 그것에 속한 정보들이 있어야 하는데 이때 쓰이는 지시자가 #include 이다 .

• #include 는 파일과 헤더파일들 사이에 컴파일 의존성을 엮어버린다 . 즉 이것에 엮여 있는 모든 것들이 이것에 의존하게 되는 것이다 .

• 의존성이 높아져버리면 컴파일이 꼬리에 꼬리를 물어 굉장한 성능저하를 가져올 수 있다 .

• 위의코드만 가지고 Person 클래스가 컴파일 되지 않는다 . Person 의 구현의 한 부분인 string, Date, Address등 연관된 것들의 정보가 있어야 컴파일 된다 . 이때 쓰는 것이 #include 지시자이다 . 즉 #include “date.h”, #include “address.h”, #include <string> 선언되어야 한다 .

인터페이스와 구현을 둘로 나누자• 정의부에 대한 의존성을 선언부에

대한 의존성으로 바꿈-> 컴파일 의존성을 최소화하는 원리

• 객체 참조자 및 포인터로 충분한 경우 객체를 직접 쓰지 않는다 .

• 할 수 있으면 클래스 정의 대신 클래스 선언에 최대한 의존하도록 한다 .-> 클래스를 사용하는 함수를 선언할 때는 그 클래스의 정의는 굳이 가져오지 않아도 된다 .

• 선언부와 정의부에 대해 별도의 헤더파일 제공

• 어떤 클래스를 사용하는 함수를 선언할 때는 그 정의는 필요 없음

• 또는 이렇게 헤더파일을 만드는데 선언만 되어있는 헤더파일만으로도 가능

항목 31 정리

• 컴파일 의존성을 최소화하자

• 컴파일 의존성을 최소화하는 아이디어-> ‘ 정의’ 대신에 ‘선언’에 의존하게 만들자

• 라이브러리 헤더는 그 자체로 모든 것을 가줓어야 하며 선언부만 갖고 있는 형태여야 한다 .

항목 32 : public 상속은 is-a관계

• public 상속은 “ is-a” 를 의미한다

• Cat 클래스를 Animal 클래스로부터 상속을 통해 파생시켰다면 이 뜻은 ‘ Cat 타입으로 만들어진 모든 객체는 Animal 타입의 객체이지만 그 반대는 성립하지 않는다’ 이다

• Animal 객체가 쓰일 수 있는 모든 곳에는 Cat 객체도 쓰일 수 있다고 단정하는 것

• cat is a Animal 이 되지만 반대인 Animal is cat은 말이 안된다 .

항목 32 : public 상속은 is-a관계

• Bird 객체인 a 는 수영을 할 수 없다 .

• Penguin 객체인 b 는 수영을 할 수 있다 .

• Penguin 객체인 b 는 날 수 없지만 Bird 에 기본 함수는 날 수 있다고 되어있기에 , 가상함수를 이용하여 재정의-> 인간의 언어 때문에 클래스 설계에 있어 오류를 범하는 경우가 많이 있다 . 설계시 주의해야 하는 사항

항목 31 정리

• public 상속의 의미는 “ is-a” 관계이다 . 기본 클래스에 적용되는 모든 것들이 파생 클래스에도 그대로 적용이 된다 . -> 이는 파생 클래스 객체도 기본객체의 일종으로 보기 때문이다 .

항목 33 : 상속된 이름을 숨기는 일을 피하자

• C++ 컴파일러는 유효범위 안에서 어떤 변수의 이름을 만나면 일단 자신이 처리하는 유효범위를 뒤져서 같은 이름이 있는지 확인 .

• 그 유효범위에 있으면 더이상 탐색하지 않음

• 그 유효범위에 없다면 다음 영역의 유효범위를 탐색

• 컴파일러는 자신이 처리하고있는 곳의 유효범위부터 탐색해 나간다 .

클래스에서의 이름 가리기

• 파생 클래스 Dervied 의 mf1은 Base 의 mf1(int) 를 가린다 .

• 파생 클래스 Dervied 의 mf3 는 Base 의 mf3(), mf3(double) 을 가린다 .-> 이러한 이유는 파생클래스에서 멀리 떨어져 있는 기본 클래스의 오버로드 버전을 상속하는 것을 ‘실수’로 간주하겠다는 컴파일러의 의도 때문이다 .

• Derived 클래스의 mf1, mf3 로 인해 오버로드 된 Base 클래스의 mf1, mf3 또한 가려지게 된다 .

using 키워드 이용

• 파생 클래스에 의해 가려진 기본 클래스의 오버로드된 함수들은 using 키워드를 통해 끄집어낼 수 있다 .

• 즉 , 오버로드된 함수들 중에 재정의를 하고 싶은것이 있다면 파생클래스에서 재정의를 하고 , using 키워드를 붙여주면 된다 .

• using 키워드를 통하여 기본클래스의 mf1, mf3 의 이름이 derived영역에서도 유효하게 되었다

항목 33 정리

• 파생 클래스의 이름은 기본 클래스의 이름을 가린다

• public 상속에서의 이런 이름 가림 현상은 바람직하지 않다

• 가려진 이름을 다시 볼 수 있게 하는 방법으로 using 선언 혹은 전달 함수를 쓸 수 있다 .

항목 34 : 인터페이스 상속과 구현상속의 차이 알기

• pubilc 상속의 개념은 함수 인터페이스의 상속과 더불의 함수 구현의 상속

• 인터페이스 -> 함수의 선언구현 -> 함수의 정의

• public 으로 상속을 받았다는 것은 기본적으로 인터페이스 + 구현 까지 상속을 받은 것이다 . Derived 는 Base 를 public 으로 상속받으므로 Base 의 인터페이스 및 구현을 상속 받은 것 .

순수가상 함수는 인터페이스 상속을 뜻한다

• 순수 가상 함수를 상속받는 파생 클래스에서는 그 가상함수를 다시 선언해야 한다 .

• 순수 가상 함수는 전형적으로 추상 클래스 안에서는 정의를 갖지 않는다 .

• 순수 가상 함수를 선언하는 목적은 파생 클래스에게 함수의 인터페이스만을 물려주는 것 . • 부모 클래스인 Shape 에서 Draw 는 순수 가상

함수로 선언 . 즉 파생클래스인 Triangle 에서 다시 선언 및 구현을 해주어야 한다 .

비순수가상 함수는 인터페이스 상속과 더불어 그 함수의 기본적 구현을 물려받게 함

• 비순수 가상 함수는 인터페이스와 더불어 기본적 구현을 물려받게 하는 방법

• 파생 클래스에서 따로 재정의 하지 않아도 된다 -> 기본적 구현을 물려받았기 때문에

• 파생클래스에서 따로 재정의 해도 된다-> 가상함수로 인터페이스 상속과 더불어 구체적인 구현도 가능하기 때문에

• 기본 클래스의 Error 은 비순수가상 함수로 이에대한 인터페이스와 기본적인 구현을 물려받음

비가상 함수는 인터페이스와 필수적인 구현까지 물려받음

• 비가상 함수를 선언하는 목적은 파생 클래스가 함수 인터페이스와 더불어 그 함수의 필수적인 구현을 물려받게 하는 것

• 비가상 함수는 클래스 파생에 관계없이 동작에 있어 변하는 것이 없을 때 사용

• 즉 , 파생클래스에서 재정의 하는 것이 아니다 .

• SetName 과 GetShapeName 함수는 비가상 함수인데 , 파생클래스라 하더라도 이름을 정하고 받아오는 함수는 동작에 변화가 없으므로 비가상 함수로 선언 .

항목 34 정리• 인터페이스 상속은 구현상속과 다르다

• public 상속은 기본 클래스의 인터페이스를 모두 물려 받는다

• 순수 가상 함수는 인터페이스만 상속

• 비순수 가상 함수는 인터페이스 상속과 더불어 기본 구현의 상속도 가능

• 비가상 함수는 인터페이스 상속과 더불어 필수 구현의 상속

항목 35 : 가상 함수 대신 쓸 것들도 생각해두자

• 비가상 인터페이스 관용구를 사용하자 : 공개되지 않은 가상 함수를 비가상 public 멤버 함수로 감싸서 호출

• 가상 함수를 함수 포인터 데이터 멤버로 대체하자

• 가상 함수를 tr1::function 데이터 멤버로 대체하여 호환되는 시그너처를 가진 함수호출성 개체를 사용할 수 있도록 하자

• 한쪽 클래스 계통에 속해 있는 가상 함수를 다른 쪽 계통에 속해 있는 가상 함수로 대체하자

항목 35 정리• 가상 함수 대신에 쓸 수 있는 다른 방법으로 NVI

관용구 및 전략 패턴을 들 수 있다 이중 NVI 관용구는 그 자체가 템플릿 메서드 패턴의 한 예시

• 객체에 필요한 기능을 멤버 함수로부터 클래스 외부의 비멤버 함수로 옮기면 , 그 비멤버 함수는 그 클래스의 public 멤버가 아닌 것들을 접근할 수 없다는 단점이 생김

• tr1::function 객체는 일반화된 함수 포인터처럼 동작 . 이 객체는 주어진 대상 시그너처와 호환되는 모든 함수호출성 개체 지원

항목 36 : 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물

• 비가상 함수는 정적 바인딩 -> 빌드 중에 이름 , 식별자 , 값 , 속성들의 내부요소를 정의 .

• 가상 함수는 동적 바인딩-> 실행중에 이름 , 식별자 , 값 , 속성들의 내부요소를 정의 .

• 자기 자신의 객체가 아닌 객체를 가리키는 포인터 타입에 의해 어떤 함수를 호출할 지 결정 .

• 비가상 함수를 public 으로 상속받으면 인터페이스와 필수 구현을 그대로 물려받음 -> 파생클래스에서 재정의하는 것은 모순 (Triange is a Shape 이 성립하지 않음 )

• Shape 클래스에서 비가상 함수를 구현하고 이를 Triangle 클래스가 상속을 받았는데 , 다시 그 비가상함수를 재정의 하고 있다 . 해당 객체 자신( Triangle 객체 a ) 이 아닌 그것을 가리키는 포인터 pT 에 의해 좌우된다 .

항목 36 정리

• 상속받은 비가상 함수를 재정의하는 일은 절대로 하면 안된다 .

• public 상속 관계의 “ is- a” 관계에 모순이 생김

• 재정의 할 필요가 있는 함수라면 가상 함수로 정의하는 것이 맞다 .

항목 37: 어떤 함수에 대해서도 상속받은 기본 매개변수 값은 절대로 재정의하지말자

• 비가상 함수는 정적 바인딩 -> 빌드 중에 이름 , 식별자 , 값 , 속성들의 내부요소를 정의 .

• 가상 함수는 동적 바인딩-> 실행중에 이름 , 식별자 , 값 , 속성들의 내부요소를 정의 .

• 기본 매개변수는 정적 바인딩 • Triangle 의 객체를 생성해서 Triangle 의 func() 를 실행하면 size = 1 이 되어 나온다 . 즉 , Triangle 클래스에서 매개변수를 재정의한 것이 의미가 없다는 것이다 .( 매개변수는 기본 클래스 것을 이용 ) -> 꼬여버림

비가상 인터페이스를 이용하여 해결

• 파생 클래스에서 재정의할 수 있는 가상 함수를 private 멤버로 둔다 .

• 이 가상 함수를 호출하는 public 비가상 함수를 기본 클래스에 만든다 .

• 비가상 함수가 기본 매개변수를 지정할 수 있게 한다 .

• func 를 비가상함수로 만들고 , 재정의할 수 있는 함수 Dofunc 를 만들어 호출 . 비가상 함수가 기본 매개변수를 지정하도록 할 수 있음 .

항목 37 정리

• 상속받은 기본 매개변수 값은 절대로 재정의하면 안된다 .

• 기본 매개변수는 정적으로 바인딩

• 가상함수는 동적으로 바인딩

• 함수호출이 꼬여버리는 경우가 생길 수 있다 .

항목 38: “has-a” 혹은 “ is-implemented-in-terms-of” 를

모형화할 때는 객체 합성을 사용• 합성이란 어떤 타립의 객체들이

그와 다른 타입의 객체들을 포함하고 있을 경우에 성립하는 그 타입들 사이의 관계

• 합성 대신에 레이어링 , 포함 , 통합 , 내장 등으로도 쓴다 .

• …has a~ : … 은 ~ 를 가진다

• …is-implemented-in-terms-of~ : … 은 ~ 를 써서 구현된다 .

• Person   객체는 이름 , 주소 , 핸드폰전화번호 등을 가지고 있는 객체이므로 , Person 과 string m_Name, Address m_Address, PhoneNumber m_celPhone 은 “ has - a “ 관계 .

• Person has a name;

• Person has a Address;

• public 으로 상속을 받는다면 이는 is-a 관계이다 .

• STL 의 set 클래스를 만드는데 list STL 을 이용하고자 한다 .

• 이때 set 은 set is-implemented-in-terms-of list 가 되는 것이고 이 두 관계가 되는 것이다 .

• 위의 관계로 is-a 로 해석해버리면 set is a list 가 되어야 하는데 list 는 중복을 허용하지만 set 은 그렇지 않다 . 즉 성립하지 않음을 알 수 있다 .

• 밑의 관계로 is-implemented-in-terms-of 가 되면 set 은 list 를 이용하여 만들어졌다 로 자연스럽게 해석할 수 있다 .

항목 38 정리

• 객체 합성의 의미는 public 상속이 가진 의미와 완전히 다르다

• 응용 영역에서 객체 합성의 의미는 has-a 이다

• 구현 영역에서 객체 합성의 의미는 is-implemented-in-terms-of 이다 .

항목 39: private 상속은 심사숙고해서 구사하자

• private 상속은 분명히 is-a 를 뜻하지 않는다 .

• private 로 상속 받으면 컴파일러는 일반적으로 파생 클래스 객체를 기본 클래스 객체로 변환하지 않는다 .

• 기본 클래스로부터 물려받은 멤버는 파생 클래스에서 모조리 private 멤버가 된다 .

• private 상속의 의미는 is-implemented-in-terms-of, 인터페이스는 물려받지 못하고 구현만 물려받을 수 있다 .

• S 는 더이상 Person 으로 간주되지 않는다 . 다만 S는 Person 의 몇 가지 특성을 사용한다는 뜻이다 .

객체 합성과 private 상속의 차이• 객체 합성도 is-implemented-

in-terms-of 의 관계를 갖는다 .

• 되도록이면 객체 합성을 사용하고 , 꼭 필요한 경우에만 private 사용-> 꼭 필요한 경우라 하면 비공개 멤버를 접근할 때 혹은 가상 함수를 재정의할 경우 .

• 어떤 클래스에서 필요한 기능이 있는데 이것을 public 상속을 받는다면 is-a 관계가 되어 모순이 일어난다 . 따라서 이렇게 어떤 기능을 이용할 때에는 private 상속을 받을 수 밖에 없다 .

• private 상속은 public 상속과 객체합성을 이용하여 피할 수 있다 . 하지만 관계는 더 복잡해진다 .

항목 39 정리

• private 상속의 의미는 is-implemented-in-terms-of 이다

• 파생 클래스 쪽에서 기본 클래스의 protected 멤버에 접근해야 할 경우 , 상속받은 가상 함수를 재정의해야 할 경우에 private 상속을 고려해보아야 함

• 객체 합성과 달리 , private 상속은 공백 기본 클래스 최적화 (EBO) 를 활성화시킬 수 있다 .

항목 40: 다중 상속은 심사숙고해서 사용하자

• 다중 상속은 단일 상속보다 구조가 복잡

• 다중 상속하면 둘 이상의 기본 클래스로부터 똑같은 이름 ( 멤버 함수 , 멤버 변수 등 ) 을 물려받을 가능성이 생긴다 .

• Son 객체인 me 는 Father 와 Mother 의 다중 상속을 받고 있고 , 다시 Father 와 Mother 는 Person 으로 부터 단일 상속을 받는다 . 이때 Son 객체의 Do() 를 호출 하면 Father 의 것인지 Mother 의 것인지 알 수 없기에 에러가 발생한다 .

모호성을 없애기 위해 호출할 기본 클래스의 함수를 손수 지정

• 모호성을 없애기 위해 호출할 기본 클래스를 지정해주어야 한다 .

• 만약 데이터멤버의 중복생성을 원한 것이 아니었다면 데이터 멤버를 가진 클래스를 가상 기본 클래스로 만들어야 한다 .-> 기본 클래스가 상속되는 경로 갯수만큼 데이터가 중복생성 되는 걸 막기 위해서

• 가상 상속을 사용하는 클래스는 가상 상속을 쓰지 않는 클래스보다 크기가 크고 , 속도도 느리다-> 가상 상속은 비싸다

• me.Father::Do() 로 호출할 기본 클래스를 지정해줌으로써 어떤 함수를 호출할 지 컴파일러가 알게되어 , 호출이 된다 .

가상 상속• 가상 상속을 하지 않으면 Fater 와

Mater 클래스를 다중상속 받는 Son 객체가 생성 될 때 1. Father 와 Mother 의 부모인 Person 클래스가 각각 생성2. Father 와 Mother 를 상속받음

• 만약 자식에서 Person 의 허용된 데이터 및 함수를 읽고자 할 때 , Father 의 것인지 , Mother 의 것인지 알 수 없어서 모호함 -> 에러

• virtual 로 상속 받으면 Father 와 Mother 의 부모인 Person 을 한 번 만 생성 -> 조상이 Person하나이므로 Person 의 허용된 데이터 및 함수 사용 가능

• 가상상속을 통하여 조상의 함수를 바로 호출 가능

항목 40 정리• 다중 상속은 단일 상속보다 확실히 복잡하다

• 모호성이 생길 여지가 있다

• 가상 상속이 필요해질 때가 있는데 , 가상 상속을 사용하면 크기 , 속도 면에서 비가상 상속과 비교하여 좋지 않다

• 가상 상속 시 가상 기본 클래스에는 데이터를 두지 않는 것이 좋다 -> 초기화 및 대입 연산의 복잡도가 커지기 때문에