effective c++ chapter1 2_dcshin

24
We are! Digging Effective C++ Chapter 1 Chapter 2 NEXT 1기 131039 신동찬

Upload: dongchan-shin

Post on 27-Jun-2015

194 views

Category:

Technology


1 download

DESCRIPTION

This content wrote for Advanced C++ Class in NHN NEXT. 2014. spring-summer semester

TRANSCRIPT

Page 1: Effective c++ chapter1 2_dcshin

We are!

Digging Effective C++

Chapter 1Chapter 2

NEXT 1기131039 신동찬

Page 2: Effective c++ chapter1 2_dcshin

We are!

항목 1. United State of C++!

C++는 더 이상 단순 C with Classes가 아니며, 여러 개의 하위 언어를 포함하는 개념쓰는 영역에 따라 프로그래밍도 다변화

C++

객체지향 개념의 C++

템플릿 C++ STL

•절차적 프로그래밍 지원•객체지향 프로그래밍 지원•메타프로그래밍 지원

•객체 지향 개념 추가•클래스, 캡슐화, 상속으로 구현•다형성, 가상 함수로 구체화

•템플릿 메타프로그래밍•컴파일 과정에서 코드를 생성•용도에 맞춰 템플릿 구문 사용

•자주 사용하는 라이브러리•컨테이너, 반복자, 알고리즘 등

C

•기본제공 데이터타입•전처리기, 배열, 포인터 등•대부분의 기본 문법의 원천

Page 3: Effective c++ chapter1 2_dcshin

We are!

항목 2. 낭비벽을 가진 #define

전처리에서 사용하는 #define을 버리고 상수를 사용하자

이유1) 코드 전체 영역에서 변경이 발생하는 #define은 낭비이유2) #define에는 유효 범위가 없다이유3) #define 매크로를 잘못 써 그만...

해결부터 보고 가실께요

방안1) 상수 타입(const)와 친해질 것방안2) 클래스에서는 정적 멤버로 만들 것방안3) inline 함수를 쓸 것

Page 4: Effective c++ chapter1 2_dcshin

We are!

항목 2. 낭비벽을 가진 #define

#define MAX_BUFF 1024 const int MAX_BUFF = 1024;

• 컴파일 에러시 확인이 어려움컴파일에서는 이미 숫자 상수로 대체

• 코드의 크기가 늘어나는 경우 존재해당 단어를 기계적으로 대치 함

• 유효범위 개념이 없음

• 캡슐화 불가

• 컴파일러 확인 및 기호 테이블 입력

• 상수 타입은 여러 번 쓰이더라도 사본은하나만 생성

• 클래스 내부에서 정적(static) 멤버로만들어 클래스 영역에서만 사용

• 클래스의 상수 데이터 멤버는 캡슐화 됨

Page 5: Effective c++ chapter1 2_dcshin

We are!

항목 2. 낭비벽을 가진 #define

클래스 내부 상수 생성 및 활용 방법

//in header fileclass CostEstimate {private:

Static const double FudgeFactor;…

}

// in implement fileconst double CostEstimate::FudgeFactor = 1.35;

방법1)

//in header fileclass CostEstimate {private:

Static const double FudgeFactor = 1.35;…

}

// in implement fileconst double CostEstimate::FudgeFactor;

방법2) 방법3)

//in header fileclass CostEstimate {private:

Enum { NumTurns = 5 };

Int scores[NumTurns];…

}

• 정적 상수 생성 후 구현 부에서 정의 • 구식 컴파일러에서의 선언과 정의 • 컴파일 과정에서 상수가 먼저 필요한 경우• Enum hack 기법으로 상수 확보• Enum hack 특징

‖ 동작 방식이 직관적(#define 유사)‖ 쓸데 없는 메모리 할당이 없음

Page 6: Effective c++ chapter1 2_dcshin

We are!

항목 2. 낭비벽을 가진 #define

#define 매크로의 위험성을 피하라

일반 사례)

#define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) )

Int a = 5, b = 0;

CALL_WITH_MAX (++a, b); //1번CALL_WITH_MAX (++a, b+10); //2번

• 1번과 2번의 행동이 다름

• 1번은 ++a가 2회 실행최초 비교 단계에서 1회(a) 리턴 단계에서 1회

• 2번은 ++a가 1회 실행

Inline함수로 안정성 확보)

template<typename T>Inline void callWithMax( const T& a, const T& b ){

f( a > b ? A : b );}

• 임의의 T에 대해 크기 비교 함수• 동작 방식과 타입 안정성까지 확보

Page 7: Effective c++ chapter1 2_dcshin

We are!

항목 3. 라면스프 같은 const

Const는 그 어떤 외부 변경도 막아냄(변경 불가)컴파일러가 제약을 확인하고 지켜 절대 변경되지 않음

용도1) 컴파일러가 사용상의 에러를 대신 확인용도2) 어느 곳에서든 사용 할 수 있음용도3) 함수 반환 값에 사용해 안전성,효율을 확보하며 에러 감소

ex) 잘못된 대입 연산 등용도4) 클래스의 인터페이스를 이해하기 좋게 함용도5) 실행 성능 향상에 기여(reference-to-const)

주의 사항

주의1) 비트수준 상수성은 모든걸 보장하지 않음주의2) 상수 멤버, 비상수 멤버 함수가 기능적으로 같은 경우

비상수 버전이 상수 버전을 호출하도록 함

Page 8: Effective c++ chapter1 2_dcshin

We are!

항목 3. 라면스프 같은 const

정말 다 괜찮은 const, 다만 의미가 조금 달라질 뿐

Char *p = greeting;

const char *p = greeting;

char * const p = greeting;

const char * const p = greeting;

void f1 ( const Widget * pw );

void f2 (Widget const *pw);

std::vector<int> vec;...const std::vector<int>::iterator iter = vec.begin();*iter = 10;++iter;

std::vector<int>::const_iterator cIter = vec.begin();*cIter = 10;++cIter

• 비상수 포인터, 비상수 데이터• 비상수 포인터, 상수 데이터(데이터 변경시 오류)• 상수 포인터, 비상수 데이터(포인터 주소 변경시 오류)• 상수 포인터, 상수 데이터(전부 변경 금지)

• 의미 차이 없음• Widget 객체가 const

• Iter가 상수• 데이터는 변경 가능하나 ++iter에서 오류

• 데이터가 상수• 데이터는 변경시 오류, ++iter 가능

Page 9: Effective c++ chapter1 2_dcshin

We are!

항목 3. 라면스프 같은 const

비트 수준 상수성은 허용되나 문제가 있는 경우

Class CTextBlock {Public:

…char& operator[]( std::size_t position ) const{ return pText[position]; }

Private:Char *pText;

};

//main 실행

Const CTextBlock cctb( “Hello” );

Char *pc = &cctb[0];

*pc = ‘J’

• 상수 객체 선언• 상수 버전 operator[] 호출로 cctb의 포인터 획득

상수 버전이라 안전할 것으로 예상• 그러나 직접 접근으로 ‘Hello’가 ‘Jello’로 변경

-> 오류!-> mutable을 사용해 처리-> 비록 상수 멤버 함수에서도 수정 가능

Page 10: Effective c++ chapter1 2_dcshin

We are!

항목 3. 라면스프 같은 const

코드가 중복되면 비상수 버전이 상수 버전을 불러 오도록 함

Const char& operator[](std::size_t position) const{

//pseudo코드경계 검사();접근 데이터 로깅();자료 무결성 검증();

}

char& operator[](std::size_t position){

//pseudo코드경계 검사();접근 데이터 로깅();자료 무결성 검증();

}

Const char& operator[](std::size_t position) const{

//pseudo코드경계 검사();접근 데이터 로깅();자료 무결성 검증();

}

char& operator[](std::size_t position){

return const_cast<char&>( static_cast<const TextBlock&>(*this)[position] );

}

• 중복을 제거• 입력 자료형과 출력 자료형을 강제 형변환으로 일치

Page 11: Effective c++ chapter1 2_dcshin

We are!

항목 4. 새제품은 초기화를 하고 쓰자

초기화 되지 않은 값을 읽도록 내버려 두면쓰레기 값을 읽고 정의되지 않은 동작(오류) 발생

모든 객체를 사용하기 전에 항상 초기화!

Page 12: Effective c++ chapter1 2_dcshin

We are!

항목 4. 새제품은 초기화를 하고 쓰자

대입은 초기화가 아니다

ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)

{theName = name;theAddress = address;thePhones = phones;numTimesconsulted = 0;

}

• 객체에 들어온 전달인자를 대입하고 있음

• 이미 대입 전에 초기화가 되어 있었어야 함

ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones)

theName( name ),theAddress( address ),thePhones( phones ),numTimesconsulted( 0 )

{}

• 멤버 초기화 리스트를 활용해 초기화

• 그에 앞서 메모리초기화 후 함수에서 넣을 수 있음

Page 13: Effective c++ chapter1 2_dcshin

We are!

항목 4. 새제품은 초기화를 하고 쓰자

비지역 정적 객체(서로 다른 Class의 참조)들의 초기화 순서에 따라 발생하는 문제‘초기화 되기도 전에 다른 객체가 초기화 전 변수 요청‘ = 오류Singleton Pattern으로 해결!

• 비지역 정적 객체를 맡는 함수 제작 후 각 객체 넣기• 함수에서 정적 객체 선언 후 참조자 반환• 직접 접근에서 함수 호출로 대체

FileSystem& tfs();Directory& tempDir(); 등

Class FileSystem { … };

FileSystem& tfs(){

Static FileSystem fs;Return fs;

}

Class Directory { … };

Dirctory::Directory( params ){

…Std::size_t disks = tfs().numDisks();

}

Directory& tempDir(){

Static Dirctory td;Return td;

}

Page 14: Effective c++ chapter1 2_dcshin

We are!

항목 5. 컴파일러가 알아서 만드는 숨겨진 함수

비어 있는 클래스를 만들더라도 이미 숨겨져 있는 함수가 존재한다

class Empty {public:

Empty() { ... }Empty(const Empty& rhs) { ... }

~Empty() { ... }

Empty& operator=( const Empty& rhs) { ... }};

• 기본 생성자

• 복사 생성자

• 소멸자

• 복사 대입 연산자

컴파일러가 알아서 만들지만...

사용자가 우선 선언하면 만들지 않음동작의 최종 코드가 적법하고 이치에 닿아야 자동 생성

Page 15: Effective c++ chapter1 2_dcshin

We are!

항목 6. 알아서 만드는 함수를 금지 시키자

복사 방지 등의 목적을 위해 컴파일러가 만드는 함수를 금지하려면?컴파일러에게 이미 그 함수가 있음을 알려줌

Class HomeForSale {Public:

…Private:

...HomeForSale ( const homeForSale& );HomeForSale& operator= ( const HomeForSale& );

}

방법1)자동으로 생성하는 함수에 대해 선언(기능은 구현 안 함)

방법2)복사 방지 부모 클래스 사용

Class Uncopyable {Protected:

Uncopyable() {}~Uncopyable() {}

Private:Uncopyable( const Uncopyable& );Uncopyable& operator=( const Uncopyable& );

};

Class HomeForSale : private Uncopyable{Public:

…}

Page 16: Effective c++ chapter1 2_dcshin

We are!

항목 7. 다형성은 부모 클래스의 가상 소멸자가 필요해

동적 바인딩시 가상 소멸자가 없으면 자식 클래스에서 메모리 leak이 발생

기본 클래스 포인터를 통해 자식 클래스 객체가 삭제 될 때,가상 소멸자가 없으면 부모 클래스만 삭제

• 생성시 순서1. 부모 클래스2. 자식 클래스

• 가상 소멸자가 없는 경우 삭제 시1. 부모 클래스2. 연결고리 상실

• 가상 소멸자 있는 경우 삭제1. 자식 클래스2. 부모 클래스

Class TimeKeeper {Public:

TimeKeeper();Virtual ~TimeKeeper();

};

TimeKeeper *ptk = getTimeKeeper();

Delete ptk;

Page 17: Effective c++ chapter1 2_dcshin

We are!

항목 7. 다형성은 부모 클래스의 가상 소멸자가 필요해

가상 소멸자가 없는 경우 부모 클래스가 되면 절.대. 안 됨

ex) std::string 타입은 가상 소멸자가 없음

STL 컨테이너 타입은 가상 소멸자가 없음vector, list, set tr1::unordered_map 등

Page 18: Effective c++ chapter1 2_dcshin

We are!

항목 8. 소멸자에서 예외가 터진다면 막아라!

예외 좋아하는 사람은 당연히 없겠지만,C++는 예외를 내는 소멸자를 두고 보지 않음

예외 처리 해결 방법

방법1) 프로그램을 바로 끝낸다방법2) 예외를 삼켜 버린다

가장 좋은 방법!-사용자에게 맡기고 실패하면 삼킨다

Page 19: Effective c++ chapter1 2_dcshin

We are!

항목 8. 소멸자에서 예외가 터진다면 막아라!

사용자에 넘기는 것이 중요해 보이지만,소멸자에 예외를 두지 않는 것이 포인트!

Class DBConn {Public:

…Void close(){

db.close();closed = true;

}~DBConn(){

If( !closed )Try {

db.close();}Catch ( … ) {

closeLogging();}

}Private:

DBConnection db;Bool closed;

};

• 사용자가 우선 close() 함수 실행으로 처리

• 사용자가 연결을 안 닫았으면 소멸자에서 재확인 후 삭제

• 소멸자에서 직접 예외 처리가 있으면 안됨

• 사용자가 함수로 호출하면 관리 가능

Page 20: Effective c++ chapter1 2_dcshin

We are!

항목 9. 생성과 소멸 과정에서 가상함수 호출 금지

앞서 생성 순서가 기억 난다면...생성자, 소멸자에 가상 함수가 있다면 부모, 자식 클래스 생성/소멸이 비정상 작동

부모 클래스 생성자는 자식 클래스 생성자보다 앞서서 실행부모의 가상 함수는 자식 클래스 데이터가 초기화 되기 전에 요청쓰레기 값을 활용한 함수 실행 가능성

문제는 호출 순서!호출 순서에 따라전혀 다른 객체를 사용하게 됨

Page 21: Effective c++ chapter1 2_dcshin

We are!

항목 10. 대입 연산자는 *this를 반환한다

대입 연산자는 좌변 인자를 *this로 반환 하도록 함

ex)

X = y = z = 15;

동치

X = (y = (z = 15));

일종의 컨벤션(관례)!“좌변 객체의 참조자를 반환하게 만들자”

Page 22: Effective c++ chapter1 2_dcshin

We are!

항목 11. operator=에서 자기 대입을 꼭 처리하라

이상한 코드? 정상 코드!

Class Widget { … };

Widget w;…w = w;

자기 대입의 가능성을 가진 경우가 많음

Widget& Widget::operator=(const Widget& rhs){

Bitmap *pOrig = pb;pb = new Bitmap(*rhs.pb);Delete pOrig;

Return *this;}

안전코드1)Widget& Widget::operator=(const Widget& rhs){

Widget temp(rhs);

Swap(temp);

Return *this;}

안전코드2)

Page 23: Effective c++ chapter1 2_dcshin

We are!

항목 12. 객체 복사는 호적을 싹 뒤져서 완전히 복사

객체를 복사하는 것은 절대 단순하지 않다

1) 해당 클래스의 데이터 멤버를 모두 복사하고2) 클래스가 상속한 부모 클래스의 복사 함수도 호출해야 함

PriorityCustomer::PriorityCustomer( const PriorityCustomer& rhs): Customer(rhs),priority(rhs.priority)

{logCall(“PriorityCustomer copy constructor”);

}

PriorityCustomer&PriorityCustomer::operator=( const PriorityCustomer& rhs ){

logCall(“PriorityCustomer copy assignment operator”);

Customer::operator = (rhs);Priority = rhs.priority;

Return *this;}

• 코드 중복보다 정확한 복사가 더 중요하다• 부모클래스의 데이터가 초기화 되도록 함• 대입 연산자를 통해 기본 클래스 부분을 대입함

Page 24: Effective c++ chapter1 2_dcshin

We are!

Thank you :)2014. 03. 27