effective-cpp (44) 썸네일형 리스트형 항목 36: 상속받은 비가상 함수를 파생 클래스에 재정의하는 것은 절대 금물! D라는 이름의 클래스가 B라는 이름의 클래스로부터 public 상속에 의해 파생되었고, B 클래스에는 mf()라는 public 맴버 함수가 정의되어 있다고 가정해보자. 여기서의 pB->mf() 이 당연히 이런식으로 동작할 것이라고 예상할 수 있다. 왜? 둘 다 같은 객체인 x에 대해 mf()를 호출하고 있기 때문이다. 하지만 당연하지 않을 수도 있다는 것이 문제이다. 특히 mf()가 비가상 함수이고 D 클래스가 자체적으로 mf() 함수를 또 정의하고 있는 경우 아래 코드와 같은 황당한 일이 벌어진다. 이렇게 같은 객체에서 같은 함수를 호출하는데, 실제로는 다른 함수를 호출하는 이유는, 비가상 함수는 정적 바인딩(static binding)으로 묶이기 때문이다.(항목 37 참고) => 즉 pB가 가르키는 .. 항목 35: 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 우리는 지금 비디오 게임 개발팀에서 일을 하고 있다. 맡은 일은 게임에 등장하는 각종 캐릭터를 클래스로 설계하는 직업이다. 근데 게임이 막 치고받고 싸우는 게임이라, 체력바가 남아나질 않는다. 그래서 healthValue() 라는 맴버 함수를 제공하기로 한다. 체력이 어떻게 계산되는지는 캐릭마다 다르기 때문에, 이 함수를 가상 함수로 선언하기로 한다. healthValue()가 순수가상함수는 아니기 때문에, 체력치를 계산하는 기본 알고리즘(기본 구현)이 제공된다는 사실을 알 수 있다. (항목 34 참고) 너무나 당연한 설계라 오히려 맥이 빠진다. 이것 말고 다른 설계도 있지 않을까? 비가상 인터페이스 관용구를 통한 템플릿 메서드 패턴 이 이야기는 "가상 함수는 반드시 private 맴버로 두어야 한다"고.. 항목 34: 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 (public)상속 이라는 개념은 사실 2가지로 나뉜다. 인터페이스 상속 vs (함수)구현 상속 이 둘의 차이는 함수 선언과 함수 정의의 차이와 맥을 같이한다고 보면 된다. 클래스 설계자의 입장에서 맴버 함수의 인터페이스(선언)만을 파생클래스에 상속받고 싶은 경우 함수의 인터페이스 구현을 모두 상속받고 싶은 경우 상속받은 구현이 오버라이드가 가능하게 만들고 싶은 경우 어떤 것도 오버라이드할 수 없도록 막고 싶은 경우 이러저러한 선택사항이 나올 수 있는데, 이러한 선택사항들 사이의 차이점을 명확하게 몸으로 느끼는 것이 중요하다. 그래서 예를 들어보면, 그래픽 응용프로그램에 쓰이는 기하학적 도형을 나타내는 클래스 계통구조를 놓고 한번 생각해보자. Shape는 추상 클래스이다. 왜냐하면 맴버 함수인 draw(.. 항목 33: 상속된 이름을 숨기는 일은 피하자 상속된 이름 => 진짜 상속이 아니라, 유효범위에 관련된 이야기이다. 여기서 cin 은 전역변수 x가 아닌, 지역변수 x를 참조한다. 왜냐하면 안쪽 유효범위에 있는 이름이 바깥쪽 유효범위에 있는 이름을 가리기 때문이다. 컴파일러가 someFunc()의 유효범위 안에서 x라는 이름을 만나면 자신이 처리하고 있는 유효범위(지역 유효범위, local scope)안에서 같은 이름을 가진 것을 찾는다. 지역 유효범위 안에 찾는 이름이 있으면 더 이상 탐색하지 않는다. 지역 유효범위 안에 찾는 이름이 없으면 전역 유효범위(global scope) 안에서 같은 이름을 가진 것을 찾는다. => 이름 탐색에 type은 관계가 없다!! => 겹치는 이름들의 type이 같든 안같든, 현재 중요한 것은 x라는 이름의 doub.. 항목 32: public 상속 모형은 반드시 "is-a(…는 ...의 일종이다)"를 따르도록 만들자 public 상속은 "is-a(…는 ...의 일종이다.)" 를 의미한다. 이 말은 꼭 기억하도록 하자. 만약 Derived 클래스를 Base 클래스로 부터 public 상속을 통해 파생시켰다면, 이것은 컴파일러에게 다음과 같이 말한 것이다. "Derived 타입으로 만들어진 모든 객체는 또한 Base 타입의 객체이지만, 그 반대는 되지 않는다" 즉 Base는 Derived 보다 더 일반적인 개념을 나타내며, Derived는 Base보다 더 특수한 개념을 나타낸다. 밴다이어그램으로 표현하면 다음과 같은 의미일 것이다. C++는 public 상속을 이렇게 해석하도록 문법적으로 지원하고 있다. 위 예제는 public 상속에서만 통한다. public 상속 = is-a 관계 자연어에 낚인 케이스이다. "새는 날 .. 항목31: 파일 사이의 컴파일 의존성을 최대한 줄이자 클래스 하나가 맘에 안들어서 소스 코드 한 줄을 수정했다. 인터페이스도 아니고 구현부인데다가 외부에 노출도 안되는 코드이다. 컴파일하는데 몇 초 안걸릴 거 같다. 빌드를 눌렀다. 느낌이 이상하다. 한참을 기다려도 빌드가 안끝난다. 엄; 문제의 핵심 : C++는 인터페이스와 구현의 분리가 깔끔하게 안되있다. 정확히 말하면 이 둘을 분리하는 문법적 장치같은것이 없음 => 사용자가 알아서 분리해야함 C++의 클래스 정의(class definition)은 클래스 인터페이스만 지정하는 것이 아니라 구현까지 상당히 많이 지정하고 있다. 위 코드만 가지고는 Person 클래스가 절대 컴파일되지 않겠다. => string, Date, Address가 어떻게 정의됬는지 모르기 때문이다. => 이들의 정의를 어디선가 가져.. 항목 30: 인라인 함수는 미주알고주알 따져서 이해해 두자. 인라인함수 = 함수 호출문 -> 함수 본문으로 바꿔치기 인라인 함수의 장점 인라인 함수 => 함수처럼 보이고, 함수처럼 동작하고, 매크로보다 훨씬 안전하고(항목 2 참조), 함수 호출 시 오버헤드도 걱정할 필요 없고 + 컴파일러 최적화는 함수 호출이 없 코드가 연속적으로 이어지는 구간에 보통 적용되도록 설계되었기 때문에, 인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별(context-specific) 최적화를 걸기가 쉽다. 아웃라인 함수엔 적용X 인라인 함수의 단점 목적 코드의 크기가 커진다!!! => 제한된 메모리 환경에서는 코드가 메모리에 다 올라가지 못할 수도 있고, 가상 메모리 환경이더라도 페이징 횟수가 늘어나고, 명령어 캐시 적중률이 떨어질 가능성이 높다. [운영체제 OS] 메모리 관.. 항목 29: 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자! 예외 안정성에 이보다 나쁜 함수가 없다. 예외 안정성을 가진 함수를 만들려면... 자원이 새도록 만들지 않는다. 위 코드는 만약 new 에서 예외를 던지면 unlock()이 실행되지 않기 때문에 뮤텍스가 계속 lock 된 상태로 남게 된다. 자료구조가 더럽혀지는 것 하용하지 않는다. 위 코드는 만약 new 에서 예외를 던지면 bgImage가 가르키는 객체는 이미 사라진 후이고, 새 그림이 깔리지도 않았는데 imageChanges는 1 증가하고, 여튼 문제가 많다. ====================================================================== 자원 누출 문제부터 해결해보자. => 자원 관리 객체를 사용하면 됨 항목 13을 참고하자. 일단 자원(mutex)가 .. 항목 28: 내부에서 사용하는 객체에 대한 '핸들'을 반환하는 코드는 되도록 피하자. 사각형(Rectangle)을 사용하는 응용프로그램을 만드는 중이다.. 사각형은 좌측상단 꼭짓점과 우측하단 꼭짓점으로 나타낼 수 있다. 그리고 메모리 부담을 줄여야 한다. => 사각형의 영역을 정의하는 꼭짓점을 Rectangle 자체에 넣기 보다, 꼭짓점을 별도의 클래스(RecData)에 넣은 후 Rectangle은 이 구조체를 가르키도록 했다. 사용자가 꼭짓점 정보가 필요할 때가 있으니 꼭짓점 정보를 반환하는 upperLeft()와 lowerRight()를 선언했는데, const인데 Point&(꼭짓점에 대한 핸들)를 반환했다. => 함수 자체에선 내부 정보를 안건드니까 const로 해도 에러가 안뜨지만, 반환된 Point 핸들은 아무 제약이 없으므로, 실질적으로 데이터가 바뀔 수 있다. 즉 이런 코드가.. 이전 1 2 3 4 5 다음