항목 36 : 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! 1 2 3 4 5 6 7 class B { public : void mf(); ... }; class D: public B {...}; B나 D, 혹은 mf에 대해 전혀 모르는 상태에서 D 타입의 객체인 x가 다음처럼 있다고 할 때, 1 D x; // x는 D 타입으로 생성된 객체입니다. 다음과 같이 작성한 코드가, 1 2 3 B *pB = &x; // x에 대한 포인터를 얻어냅니다. pB->mf(); // 이 포인터를 통해 mf를 호출합니다. 다음처럼 동작하지 않으면 꽤나 황당할 것입니다. 1 2 3 D *pD = &x; // x에 대한 포인터를 얻어냅니다. pD->mf(); // 이 포인터를 통해 mf를 호출합니다. 황..
항목 35 : 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 객체 지향 설계에 대한 여러 가지 방식을 학습해 봅시다. 비가상 인터페이스 관용구를 통한 템플릿 메서드 패턴 사용자로 하여금 public 비가상 멤버 함수를 통해 private 가상 함수를 간접적으로 호출하게 만드는 방법으로, 비가상 함수 인터페이스 관용구라고 많이 알려져 있습니다. 이 관용구는 템플릿 메서드라 불리는 디자인 패턴을 C++ 식으로 구현한 것입니다. 이 관용구에 쓰이는 비가상 함수를 가상 함수의 랩퍼라고 부르기도 합니다. 함수 포인터로 구현한 전략 패턴 가상 함수를 함수 포인터 데이터 멤버로 대체합니다 군더더기 없이 전략 패턴의 핵심만을 보여주는 형태입니다. tr1::function으로 구현한 전략 패턴 가상..
항목 34 : 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 상속이라는 개념은 두 가지로 나뉩니다. 하나는 함수 인터페이스의 상속이고, 또 하나는 함수 구현의 상속입니다. 특정 클래스가 그 클래스에서 파생된 클래스에 대해 미치는 영향은 절대적입니다. 그 이유는 멤버 함수 인터페이스는 항상 상속되게 되어 있기 때문입니다. 순수 가상 함수는 두 가지 특징을 가지고 있습니다. 첫째, 어떤 순수 가상 함수를 물려받은 구체 클래스가 해당 순수 가상 함수를 다시 선언해야 합니다. 둘째, 순수 가상 함수는 전형적으로 추상 클래스 안에서 정의를 갖지 않습니다. 이 두가지를 조합해보면 하나의 결론이 나옵니다. 순수 가상 함수를 선언하는 목적은 파생 클래스에게 함수의 인터페이스만을 물려주려는 것이다. 단순..
항목 33 : 상속된 이름을 숨기는 일은 피하자 1 2 3 4 5 6 7 8 int x // 전역 변수 void someFunc() { double x; // 지역 변수 std::cin>>x; // 입력을 받아, 지역 변수 x에 새 값을 읽어 넣습니다. } Colored by Color Scripter ------------------------------------------------------------------------------------- | 전역 유효 범위 | | X | | ___________________________| | | someFunc의 유효범위 | | | | | | | -----------------------------------------------------------..
항목 32 : public 상속 모형은 반드시 "is-a(...는 ...의 일종이다)"를 따르도록 만들자 public 상속은 반드시 "is-a" 관계를 뜻해야 합니다. 가상 함수의 의미는 "인터페이스가 상속되어야 한다"인 반면, 비가상 함수의 의미는 "인터페이스와 구현이 둘 다 상속 되어야 한다"입니다. public 상속은 기본 클래스 객체가 가진 모든 것들이 파생 클래스 객체에도 그대로 적용된다고 단정하는 상속입니다. 클래스들 사이에 맺을 수 있는 관계로 is-a 관계만 있는 것은 아닙니다. 두 가지가 더 있는데, 하나는 "has-a(...는...를 가짐)"이고 또 하나는 "is-implemented-in-terms-of(...는...를 써서 구현됨)"입니다. 꼭 잊지 말아야 할 것! public 상속..
항목 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자 객체 참조자 및 포인터로 충분한 경우에는 객체를 직접 쓰지 않습니다. 어떤 타입에 대한 참조자 및 포인터를 정의할 대는 그 타입의 선언부만 필요합니다. 반면, 어떤 타입의 객체를 정의할 때는 그 타입의 정의가 준비되어 있어야 합니다. 할 수 있으면 클래스 정의 대신 클래스 선언에 최대한 의존하도록 만듭니다. 어떤 클래스를 사용하는 함수를 선언할 때는 그 클래스의 정의를 가져오지 않아도 됩니다. 심지어 그 클래스 객체를 값으로 전달하거나 반환하더라도 클래스 정의가 필요없습니다. 선언부와 정의부에 대해 별도의 헤더 파일을 제공합니다. "클래스를 둘로 쪼개자"라는 지침을 제대로 쓸 수 있도록 하려면 헤더 파일이 짝으로 있어야 합니다. 하나는 선언부를 위..
항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자 인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 실제로 대부분의 컴파일러는 '아웃라인' 함수 호출에 대해 이런 최적화를 적용하지 않습니다. 인라인 함수 사용시 코드의 크기가 커질수 있으며 이는 성능의 걸림돌이 될 수도 있습니다. 하지만 본문 길이가 굉장히 짧은 인라인 함수를 사용할 경우 함수 본문에 대해 만들어지는 코드의 크기가 함수 호출문에 대해 만들어지는 코드보다 작아질 수도 있습니다. 인라인 함수는 대체적으로 헤더 파일에 들어 있어야 하는게 맞습니다. 대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문입니다. 인라인 함수 호출을 그 함수의 본문으로 바꿔치기하려면 컴파일러가 그 함수가 어떤 ..
항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 예외 안전성을 확보하려면 두 가지의 요구사항을 맞추어야 합니다. 1. 자원이 새도록 만들지 않습니다. 2. 자료구조가 더럽혀지는 것을 허용하지 않습니다. 예외 안전성을 갖춘 함수는 세 가지 보장중 하나를 제공합니다 1. 기본적인 보장 : 함수 동작 중에 예외가 발생하면, 실행 중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠 다는 보장입니다. 어떤 객체나 자료구조도 더럽혀지지 않으며, 모든 객체의 상태는 내부적으로 일관성을 유지하고 있습니다(즉, 모든 클래스 불변속성이 만족된 상태입니다). 2. 강력한 보장 : 함수 동작 중에 예외가 발생하면, 프로그램의 상태를 절대로 변경하지 않겠다는 보장입니다. 이런 함수를 호출하는 것은 원..
항목 28 : 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자 핸들을 반환하는 함수는 문제를 발생시킬수 있습니다. 예를 들면, 무효참조 핸들입니다. 핸들이 있기는 하지만 그 핸들을 따라갔을 때 실제 객체의 데이터가 없는 것이죠. 객체의 내부에 대한 핸들을 반환하는 함수는 위험합니다. 이유는 간단합니다. 바깥으로 떨어져 나간 핸들이 그 핸들이 참조하는 객체보다 더 오래 살 위험이 있기 때문입니다. 꼭 잊지 말아야 할 것! 어떤 객체의 내부요소에 대한 핸들(참조자, 포인터, 반복자)을 반환하는 것은 되도록 피하세요. 캡슐화 정도를 높이고, 상수 멤버 함수가 객체의 상수성을 유지한 채로 동작할 수 있도록 하며, 무효참조 핸들이 생기는 경우를 최소화할 수 있습니다.
항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자 이번 항목에서는 캐스팅에 대해 소개해보도록 하겠습니다. 일단 캐스팅 문법 정리부터 시작하도록 하겠습니다. 똑같은 캐스트인데 쓰는 방법이 세 가지나 있습니다. 우선 C 스타일의 캐스트입니다. (T) 표현식 // 표현식 부분을 T 타입으로 캐스팅합니다. 다음은 함수 방식 캐스트입니다. T (표현식) // 표현식 부분을 T 타입으로 캐스팅합니다. 이 두 형태를 통틀어 '구형 스타일의 캐스트'라고 부르겠습니다. C++는 네 가지로 이루어진 새로운 형태의 캐스트 연산자를 독자적으로 제공합니다(신형 스타일의 캐스트 혹은 C++ 스타일의 캐스트 라고 부르죠) const_cast(표현식) dynamic_cast(표현식) reinterpret_cast(표현식) stat..