effective c++ 3

114
Effective C++_3 131043 양현찬 NHN NEXT

Upload: -

Post on 12-Jul-2015

294 views

Category:

Software


8 download

TRANSCRIPT

Effective C++_3131043 양현찬

NHN NEXT

구현

변수의 정의는 나중에

비밀번호가 너무 짧으면 예외를 던지도록 구현

예외가 발생하면 encrypted는 사용하지 않게 된다

이번에는 예외처리 다음에 변수를 정의하고 있지만

정의 다음에 대입을 하고 있다

다시 한번 이야기하지만 초기화와 대입은 다르다.

올바른 코드

이건 어떠냐!

당신의 습관은 어느 쪽인가? 나는 왼쪽이었다

생성 + 소멸자 > 대입연산 = A방법

생성 + 소멸자 < 대입연산 = B방법

A의 경우 변수 w의 유효범위가 넓어지기 때문에 수행성능에 민감한 부분이라면 B방법

캐스팅 쓰지마!

C++의 악당들

캐스팅에 관한 이해

명시적으로 static_cast를 사용합시다

하나의 객체가 가질 수 있는 주소는 하나 이상이다….

다중상속 뿐만 아니라 단일상속에서도 일어나는 현상이다

컴파일러마다 메모리를 관리하는 방법이 다르다.

캐스팅 조심하자

함수호출이 이루어지는 객체는 현재의 객체가 아니다!

캐스팅이 일어나면서 this의 기본 클래스 부분의 사본이 생성된다.

dynamic_cast

• 정말 느리다

• 정~~~말 느리다

• 느리다

• 아주 느리다

• 마지막으로, 느리다

blink()함수를 사용하고 싶어서 dynamic_cast를 사용하고 있다.

SpecialWindow 이외 Window에서 파생되는 다른 객체를 vector에 담을 수 없지만 캐스팅을 사용하지 않고 있다.

혹은 가상 함수를 기본 클래스에 구현한다.

정말 끔찍한 코드

만약 Window의 파생 클래스 계열이 바뀌면 지옥이 펼쳐진다.

내부 객체의 핸들을 반환하지 말자

여기서 핸들이란

• 다른 객체에 손을 댈 수 있게 하는 매개자

• 참조자, 포인터, 반복자 등등 전부 핸들

• 윈도우 시스템의 핸들도 핸들

• 자동차 핸들도 핸들…

• 핸들이란 사람의 이름도 핸들…….

Private으로 선언된 멤버 변수의 참조자를 반환하고 있다. Public이나 다름없어 진다.

Const를 통해 수정을 금지하고 있다.

여전히 무효참조 핸들에 대한 위험을 안고 있다.

만약 pData가 메모리에서 해제되어버리게 된다면?

반환된 const Point& 값을 가지고 있는 변수가 있다면

내용이 사라진 주소 값만 물고 있다.

되도록이면 최대한 피하자! 값만 제공하는 용도라면 사본을 반환할 수도 있다.

예외의 안전성이 확보되는 그날을 위해

느낌 so bad

예외가 잘 처리되었다면?

우선 자원부터

앞에서 자원을 관리하는 방법을 배웠습니다.

다음은 자료구조 오염

•기본적인 보장

•강력한 보장

•예외불가 보장

•보험가입은 이순재씨와 함께

기본적인 보장

• 예외가 일어나도 계속해서 작업이 가능하도록 보장

• 모든 것들이 유효한 상태로 유지

• 내부적으로 일관성 유지

• 하지만 프로그램 상태가 정확히 어떠한지는 파악하기 힘들다

강력한 보장

• 예외가 발생하면 없었던 일로하고 상태를 되돌린다

• 이러한 함수의 동작을 원자적 동작이라고도 함

• 쓰기 편하다.(예측 가능한 상태가 두 가지 뿐이다)

예외불가 보장

• 예외가 절대로 없다는 것을 보장한다

• 한번 약속은 영원한 약속

• 뭐, 할 수 있으면 한번 해보세요.

코드로 돌아가서

New실행 도중 예외가 발생하면 reset이 동작하지 않아 reset속의 delete도 동작하지 않는다.

copy-and-swap

사본을 만들고 사본을 수정한다예외가 발생하면 사본만 사라진다.강려크한 보장이다.

그러나 함수 전체가 강려크한 보장을 하지 못한다

f(1)이 기본적인 보장일 경우 f(1)의 실행으로 인해 이미 상태변화를 보장하지 못한다.

이런 경우는 항상 주의하고 넘어갈 수 밖에 없다.

inline 함수, 잘 이해하자

Inline

• Inline함수를 사용하면 문맥 별 최적화를 걸기가 용이해진다.

• 남발하면 코드가 길어져 오히려 느려질 수 있다. (용량문제도 고려해야 한다.)

• 길이가 굉장히 짧은 inline함수는 코드의 크기도 줄어들고 빨라질 수 있다.

명령이 아니라 요청

이런 경우 컴파일러가 암시적으로 inline을 걸어버린다.

일반적으로 사용되고 있는 inline함수 형태

template inline 둘 다 대체적으로 헤더파일에 들어가야 한다.

하지만 template에 의무적으로 inline이 들어가는 것은 아니다.

호출방법에 따라 다르다

Inline되지 않는 inline함수는 오히려 프로그래머의 발목을 잡는다

속지말자

생성자와 소멸자는 참 inline을 사용하기에 매우 좋아 보이지만…

위와 같이 컴파일러는 예외처리를 코드를 집어넣어버린다.

파일 사이 컴파일 의존성을 줄이자

우리의 골칫덩이들 전처리기

마음 같아서는 이렇게 전방선언을 하고 싶다.

하지만 표준 라이브러리는 전방선언이 어렵다.(위의 경우 string은typedef에 템플릿까지 사용되어 재대로 선언하기 힘들다.)

우리가 만든 클래스라도 정의가 되어있지 않으면 객체의 크기를 알 수 없어할당하기 힘들다.

Person.h

멤버 변수는 pImpl밖에 없다

모든 구현은 PersonImpl객체에서 이루어지고 있다

Person같은 클래스를 핸들 클래스라고 한다

정리

• 포인터로 충분한 경우 객체를 직접 사용하지 말자

• 클래스 정의 대신 선언에 최대한 의존하도록 하자

• 클래스를 쪼갤 경우(pimpl) 선언과 정의에 대해 각각의 헤더파일을 제공하자

• 템플릿 선언과 정의를 분리할 수 있도록 하는 기능을 export라는 키워드로 제공하고 있다.(아직은 쓰기에는 시기상조)

아직 끝나지 않았다아까의 person 클래스

팩토리 함수Person 클래스를 사용하기 위해 만든 함수

사용방법

Person의 내용을 담고 있는 함수

마지막으로 팩토리 함수 구현

이것이 클래스의 클라스

public은 “is-a”

A 펭귄 is a 버드

그런데 이상하다 펭귄은 날 수 없다.

모든 새는 날 수 있다.

플라이드 버드

현실에 가까운 객체 디자인

이렇게도 가능하지 않나요?

이건 펭귄이 못나는 것이 아니다. 펭귄이 날면 에러가 난다는 뜻이다.

만약 펭귄이 나는 기이한 현상이 일어났을 때는 런타임 중에서만 발견할 수 있다.

다음은 도형을 가지고 놀자

초등학교 때 배운 내용이다.

Rectangle클래스와 넓이를 늘리는 함수

여기서 넓이를 늘리는 함수는 가로와세로길이가 같아야 한다.

음… Rect에서 상속받은 makeBigger함수가 이상하다.

클래스간 맺을 수 있는 관계는 이뿐만이 아니다.

쫌 있으면 보게 될 것이다.

상속된 이름을 숨기는 일은 피하자

컴파일러 : mf2발견!!

유효범위부터 탐색

다음 유효범위 탐색

컴파일러 : 흠… 이건 어떨까….

Base클래스에 있는 이름은 전부 Derived클래스에 있는 이름에 의해 가려진다.

이름만 본다! 매개변수 따윈 쳐다보지도 않는다!

컴파일러는 그저 실수로 간주하고 있다.

실수가 아님을 컴파일러에게 알려주자.

기본 클래스가 가진 전부를 상속하고 싶지 않을 경우

Private사용으로 is-a관계가 깨진다.

결국은 Derived클래스의 함수를 사용하는 형태

인터페이스 상속과 구현 상속

Shap라는 추상 클래스

그저 껍데기처럼 보이지만 어마어마한 힘을 저장하고 있다.

우선 순수가상함수부터

마치 “기능은 이렇게 이렇게 해! 구현은 알아서 해!” 라고 말하고 있다.

순수 가상함수는 인스턴스를 만들 수 없지만 이렇게는 사용할 수 있다.

다음은 단순 가상함수

“구현은 해 드릴께, 마음에 안 들면 고쳐 쓰시던가 아, 대신 이거 지원하는 함수는 꼭 있어야 해”

매번 우려먹는 재미

깜빡 잊고 fly함수를 구현하는 것을 잊을 경우 원치도 않은 fly함수를 상속받을 수 도 있다.

A, B에는 같은 fly기능을 넣고 싶다.

하지만 C에는 다른 fly기능을 구현하고 싶은 경우

위험할 수 있다.

Fly는 순수가상함수로, defaultFly를 제공하고 있다.

안전하게 C의 fly기능을 구현할 수 있다.

“코드 너무 더러운데 다른 거 없어요?”

곁은 순수 가상함수로 속은 알차게

이게 가능했다니…

마지막으로 비가상 함수

무조건 이거 사용해! 패 건들지마!

주의할 점

•모든 함수를 비가상 함수로 만들지 말자

•모든 함수를 가상 함수로 만들지 말자

가상 함수를 남발하면 츤데레가 되어버린다.

우선 비용문제도 있다. 가상함수테이블이 생성되는데 필요도 없는걸 만들어야 하나?

가상함수를 대신할 것을 생각해두자

캐릭터의 체력계산은 캐릭터마다 다르게 계산해야 한다.

너무 당연한 설계다. 하지만 다른 방법이 없을까?

당연한 설계를 두고 다른 방법을 염두에 두는 것은 너무 어려운 일이다.

가상함수 은폐론

가상함수는 private으로 선언

가상함수를 사용할 수 있는 인터페이스를 제공한다.

비 가상함수 인터페이스라 한다.혹은 wrapper라고 한다.

하지만 엄격하게 private으로 선언될 필요는 없다. 상속받는 클래스에서이 부분의 사용을 염두에 두고 protected로 선언 할 수 있다.

생성자에서 체력치를 계산하는 함수를 받아들이고 있다.

이런 설계는 제법 재미있는 특징을 가진다.

tr1::function으로 구현한 패턴

체력치 계산을 꼭 함수가 해야하나?

반환형도 int로 바꿀 수 있는 임의의 타입이면 상관없지 않나?

이 부분을 강조해서 봅시다.

Tr1::function타입

이걸로 만들어진 객체는 앞으로 대상 시그니처(<>속의 타입)와 호환되는 함수호출성 개체를 어떤 것도 가질 수 있다.

여기서 호환된다는 뜻은 ‘암시적 변환이 가능하다'라는 뜻이다.

말로는 어려우니 사용방법을 봅시다.

고전적인 전략 패턴

익숙하지 않으니 코드로 보자

HealthCalcFunc클래스는 파생시킬 수 있기 때문에 채력계산 알고리즘을 발전시키기에 용이하다.

기능은 이전 클래스들과 같다.

정리합시다

상속받은 비가상 함수의 재정의는 금물!

이거 뭐.. 설명이 필요한가?

상속받은 함수들을 호출하고 싶다.

하지만 비가상 함수를 재정의 할 수 있다.

상속의 의미와 다를 뿐더러 D를 B의 포인터에 넣었을 경우D의 함수가 호출되는 등 기능의 구현이 복잡해진다.

상속의 의미 “Is-a”

이런 성질을 정적 바인딩이라 한다.

가상 함수는 동적 바인딩으로 묶여있다.

상속받은 기본 매개변수 값은 절대 재정의 하지 말자

이유는 간단하다

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

•하지만 기본 매개변수 값은 정적 바인딩으로 묶인다.

이거 뭐야…. 무서워….

객체의 정적 타입

정적 타입 Shape*형에 의해 진짜로 가리키는 대상이 달라지지 않는다.

객체의 동적 타입

//동적 타입은 Circle*

//동적 타입은 Rectangle*

이 객체가 어떻게 동작할 것이냐? 를 나타내는 타입

가상함수는 동적 타입에 의해 동작. 즉 동적으로 바인딩된다.

매개변수는 정적이기 때문에 항상 이렇게 기본값까지 적어줘야 한다.

귀찮다….

비가상 함수에서 기본 기본 매개변수를 지정하고 있다.

어차피 비가상 함수를 통해 호출된다.

“has-a” 그리고 “is-implemented-in-term-of”

객체의 합성을 이용하자

합성이란 다른 것이 아니라 바로 이거다.

왜 복잡하게 두 가지 의미인가?

•응용 영역

•구현 영역

캐릭터, hp, 몬스터 등의 의미를 가진 객체

객체는 두 가지 영역이 있기 때문이다.

버퍼, 뮤텍스, 탐색트리 등의 시스템구현을 위한 인공물

응용 – has-a

구현 – Is implemented in terms of

돌아가서 객체의 합성을 이용하자

Set이라는 자료구조를 list에서부터 다시 만들고 싶다.

하지만 list는 중복을 허용하고 Set은 중복을 허용하지 않으니 is-a관계는 부적절하다.

이럴 때는 list를 사용해서 구현되는 (Is implemented in terms of) 형태를 사용하자

private상속은 심사숙고

Public을 살짝 바꿔보면..

전혀 is-a가 아니다

Private상속

• 컴파일러가 파생 클래스를 기본 클래스로 변환하지 않는다.

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

• Is-implemented-in-terms-of라는 의미를 가진다.

• 즉 파생 클래스와 기본 클래스간의 어떠한 의미 없는 그냥 구현기법 중 하나다.

잠깐 합성하고 차이가 뭐야?

•그냥 합성을 써라

• Private을 꼭 사용해야 할 경우만 써라

•비공개 멤버에 접근하고 싶거나

•가상함수를 재정의 하고 싶거나

•어쩔 수 없이 private을 사용해야만 하거나

예를 들자면

Timer라는 클래스를 다시 재활용하고 싶다.

가상함수 onTick을 다시 정의하기 위해 private을 사용

하지만..

Private꼭 안 사용해도 구현 가능하다.

오히려 widget의 파생클래스에서 onTick의 재정의를 막을 수 있고

컴파일 의존성을 최소화 시킬 수 있는 이점이 있다.

어쩔 수 없이 private을 사용하는 경우

재미있게도 c++에서는

독립적인 객체에 관해서는 크기가 무조건 0초과이다.

하지만…

이렇게 공간절약이 가능하다. 이 방법을 공백 기본 클래스 최적화(EBO)라고 한다.

다중상속도 심사숙고

다중상속 좋다? 나쁘다?

접근할 수 있는 함수가 하나뿐임에도 불구하고 모호성을 가진다.

이는 컴파일러가 접근 권한을 알아보기 전에 중복함수호출을 결정하기 때문이다.

명시적으로 사용할 함수를 말해야 한다.

죽음의 마름모

File에 멤버변수가 하나 있다면 IOFile에는 그 변수가 몇 개 있을까?

기본적으로는 중복 생성한다.

하지만 하나만 존재하도록 만들 수 있다.

가상 상속을 사용해야 한다.

재미있는 점은 표준 C++라이브러리가 이 형태를 가지고 있다.

basic_iostream이 이 형태로 이루어져 있다.

생각해보면 모든 public상속이 전부 virtual이 붙어야 한다. 하지만 virtual을 붙였을 때 드는 비용을 생각하자

가상상속의 비용들

• 컴파일러의 숨겨진 비용뿐만 아니라 우리의 뇌 자원도 빼앗아간다.

• 초기화가 필요한 가상 기본 클래스에서 파생된 경우

• 기존의 클래스 계통에 새로 파생 클래스를 추가할 경우

• 전부 고려해야 한다

• 그냥 쓰지 말자

• 꼭 써야 한다면, 가상 기본 클래스의 내용을 최대한 줄이자.

그래도 쓸만할 때가 있다

한쪽은 public, 다른 한쪽은 private으로 상속받고 있다.

이 형태를 기억해두자.