이펙티브 C++ 스터디

12
Effective C++ chapter1,2

description

effective C++ 스터디

Transcript of 이펙티브 C++ 스터디

Page 1: 이펙티브 C++ 스터디

Effective C++ chapter1,2

Page 2: 이펙티브 C++ 스터디

용어 정리

선언코드에 사용되는 어떤 대상의 이름과 타입을 컴파일러에게 알려줌 , 추가로 이 책에서는 int 같은 기본 제공 타입도 객체라고 표현함 .

정의선언에서 빠진 구체적인 세부사항을 컴파일러에 제공

시그너쳐함수의 매개 변수 리스트와 반환 타입 ( VA 를 사용하면 refactor 항목 중에 있는 것을 볼 수 있음 )

초기화어떤 객체에 최초의 갑을 부여하는 것

기본 생성자아무 인자도 주어지지 않은 채로 호출될 수 있는 생성자 . 원래부터 매개변수가 없거나 A(), 모든 매개변수가 기본 값을 갖고 있으면 A(int x=0, bool b=0) 기본 생성자가 될 수 있다 .

복사 생성자어떤 객체의 초기화를 위해 그와 같은 타입의 객체로부터 초기화할 때 호출되는 함수 class a1(b1)

Page 3: 이펙티브 C++ 스터디

용어 정리

복사 대입 연산자같은 타입의 다른 객체에 어떤 객체의 값을 복사하는 용도로 쓰이는 함수

STL(standard template library)

표준 템플릿 라이브러리 . 컨테이너 (vector, list 등의 자료구조 ), 반복자 (vector<int>::iterator),

알고리즘 (for_each, find, sort) 들과 관련된 기능들의 집합 . 함수객체 (function object) 는 함수처럼 동작하는 C++ 객체 , operator() 를 오버로드한 클래스로 만들어짐 .

미정의 동작 (undefined behavior)

정의 되지 않은 동작들 , null pointer 를 참조하거나 유효하지 않은 배열 원소지정 번호를 참조하는 등

인터페이스 (interface)

시그너쳐 , 혹은 어떤 클래스의 접근 가능 요소 , 표현식 등 설계 아이디어로서의 인터페이스를 나타냄Java 의 인터페이스 아님 . 비슷한 개념은 나중에 나올 것 .

사용자 (client)

내가 만든 코드를 사용하게 될 프로그래머 . 나 또한 잠재적인 사용자 이다 .

Page 4: 이펙티브 C++ 스터디

C++ 은 언어들의 연합체

C

블록 , 문장 , 선행 처리자 , 기본제공 데이터타입 , 배열 포인터 등이 다 C 에서 왔음

C++

클래스에 대한 개념 , 캡슐화 , 상속 , 다형성 , 가상함수 등등 객체지향의 개념들이 다 들어있음

템플릿 C++

템플릿이 강력하여 템플릿 메타프로그래밍 (TMP) 라는 것도 있다 .

STL

STL 은 나름 독특한 사용 규약이 있다 .

Page 5: 이펙티브 C++ 스터디

#define 을 쓰려거든 const, enum, inline 을 떠올리자 .

선행 처리자보다 컴파일러를 가까이 하자 .

컴파일러는 선행 처리자의 기호식 이름은 보이지 않는다 . 따라서 이 부분에 문제가 있을 경우 , 디버깅 시 선행 처리자가 대체하고 있는 값이 어디에서 왔는지 찾기 어려울 수 있다 .

#define 을 상수로 교체할 때 주의점 - 상수 포인터를 정의하는 경우 const char * const authorName = “abcd” 등으로 포인터와 포인터가 가리키는 대상까지 const 로 선언하자 기왕 쓸거면 const std::string 으로 쓰자 .

#define 을 상수로 교체할 때 주의점 - 클래스 멤버로 상수를 정의하는 경우 선언부에 하는 경우 static 을 붙인다 .( 동일한 값들이 객체가 생성될 때마다 생길 필요는 없으므로 )

static const int NumTurns = 5;

정의부에 하는 경우는 static 으로 선언부에 선언하고 정의부에서 값만 넣어준다 .

선언 → class A { private : static const int NumTurns };

정의 → const int A::NumTurns = 5;

Page 6: 이펙티브 C++ 스터디

#define 을 쓰려거든 const, enum, inline 을 떠올리자 .

enum hack

enum { NumTurns = 5 }; 등으로 사용하면 #define 과 비슷한 효과를 낼 수 있다 . 많은 코드들이 이와 같이 쓰였으므로 알아두면 좋음

매크로 대신 inline 함수를 쓰자#define CALL_WITH_MAX(a,b) f((a)>(b)?(a) : (b))

같은 매크로는 인자마다 괄호를 씌워 두어도 a++ 같은 매개변수 사용시 두 번 호출되는 문제가 생길 수 있다 .

template<typename T>

inline void callWithMax(const T& a, const T& b)

{

f(a>b ? a:b);

}

위와 같은 형태로 쓰자 .

Page 7: 이펙티브 C++ 스터디

낌새만 보이면 const 를 들이대자

pointer 에서 const

char greeting[] = “hello”;

const char * const p = greeting;

이면 포인터도 상수고 가리키는 데이터도 상수라는 뜻 . * 뒤에 const 는 p 가 가리키는 대상도 수정할 수 없음을 뜻하고 , 앞에 const 는 그 대상이 가진 값도 수정할 수 없다는 뜻 .

함수 반환값을 const

함수 반환값이 const 일 경우 사용자 정의 operator 를 잘못 써서 대입하는 일은 일어나지 않는다 .

사용자가 정의한 operator* 에 대해 (a*b)=c; 같은 일이 벌어지는 것을 막을 수 있다 .

** const 키워드가 있고 없고의 차이만 있는 함수들은 오버로딩 할 수 있다 .

** operator [] 함수의 리턴값에 & 가 안 붙는다면 임의 클래스의 tb[0] 같은 값은 복사된 값일 뿐 본체가 아니므로 수정할 수 없을 것이다 .

Page 8: 이펙티브 C++ 스터디

낌새만 보이면 const 를 들이대자

비트수준 ( 물리적 ) 상수성과 논리적 상수성비트수준 상수성은 컴파일러가 검사해준다 . 어디서 const 로 된 멤버를 수정하려고 시도하는지를 체크해준다 . 하지만 그것만으로는 부족한 경우가 있다 .( 책의 예제에서는 상수형 포인터로 반환 받았지만 그것이 가리키는 대상까지는 상수가 아니었기 때문에 수정 가능했다 .) 따라서 실제 데이터가 바뀌더라도 사용자가 알아채지 못하기만 하면 상수 멤버 자격이 있다고 하는 것을 논리적 상수성이라 한다 .

코드 중복 피하기 ( 상수 함수에서 비상수 함수를 불러서 const떼기 )

const 가 붙고 안 붙고의 차이만 있는 함수들의 중복사용을 막기위해 cast 를 이용한다 .

static_cast<const Function>(*this) 로 상수 함수를 불러다가 const_cast 로 const 를 뗀다 . **

반대는 위험하다 !!

Page 9: 이펙티브 C++ 스터디

객체를 사용하기 전에 반드시 그 객체를 초기화 하자

객체의 값은 항상 알아서 초기화되지 않는다 .

C++ 의 C 부분 중 초기화에 런타임 비용이 소모될 수 있는 상황이라면 보장되지 않는다 .

C++ 의 STL 부분은 보장해준다 .

→ 그냥 전부다 손수 초기화하자

대입과 초기화를 헷갈리지 말자 ( 멤버 초기화 리스트를 사용하자 )

생성자 내부에서 값을 대입해주는 것은 초기화가 아니다 . 그냥 대입일 뿐생성자에서 초기화를 하려면 “멤버 초기화 리스트”를 사용하자 . boo::boo(int a, int b) : mA(a), mB(b)

{} 이런거이런 방식은 각 멤버변수들의 복사 생성자에 의해 초기화 되는 방식이다 .

** 상수이거나 참조자는 초기화 리스트에 넣는 것이 의무 이다 .

클래스 초기화의 순서를 헷갈리지 말자기본 클래스는 파생 클래스보다 먼저 초기화클래스 데이터 멤버는 그들이 선언된 순서대로 초기화 ( 멤버 초기화 리스트에 넣어진 순서대로가 아님 ,

따라서 헷갈리지 않으려면 멤버 초기화 리스트도 선언된 순서대로 넣는 것이 좋다 )

비지역 정적 객체는 초기화 순서가 보장되지 않는다 . (싱글톤 패턴 )

따라서 비지역 정적 객체는 함수 안에 넣어서 지역 정적 객체로 바꾸고 리턴값을 받아서 사용하는 방식으로 쓰자 ** 정적 객체는 자신이 생성된 시점부터 프로그램이 끝날 때까지 살아있는 객체

Page 10: 이펙티브 C++ 스터디

생성자 , 소멸자 , 및 대입 연산자

C++ 가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자없으면 자동으로 생성되는 클래스 함수들에 기본 생성자 ( 생성자가 하나도 없을 경우 ), 복사 생성자 ,

소멸자 , 복사 대입 연산자 (operator=) 등이 있다 .

알아서 만들어주는 복사 생성자와 복사 대입 생성자는 외부에서 이들을 사용하려 할 때 , 선언되며 비정적 데이터를 사본객체 쪽으로 그냥 복사한다 .

복사 대입 생성자는 상황이 애매할 땐 그냥 작동을 거부한다 .( 클래스 멤버변수로 참조자가 있을 경우 )

컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자복사 생성자나 복사 대입 생성자를 사용하면 안되는 클래스의 경우 private 으로 선언하고 정의는 하지 말자 . 그럼 외부에서 접근을 못할테고 ( 컴파일 에러 ), 클래스 내부에서나 friend 함수가 호출할 경우에도 정의가 없으므로 링크 에러가 발생한다 .

혹은 uncopyable 등의 함수를 만들어 그 안에 private 으로 넣어두고 이를 상속받아 사용하는 방법도 있다 . 이렇게 하면 내부나 friend 함수에서 호출할 경우의 에러 시점을 링커에서 컴파일로 댕길 수 있다 .

** 부스트 라이브러리의 noncopyable 이란 클래스가 있으니 이를 써도 된다 .

Page 11: 이펙티브 C++ 스터디

생성자 , 소멸자 , 및 대입 연산자

다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자부모 클래스형의 포인터 변수로 자식 클래스를 가리키는 경우 이를 delete 하면 부모 클래스의 소멸자만 호출되어 자식 클래스에서 생성한 자원은 leak 이 생기게 된다 . 따라서 자식 클래스의 소멸자를 호출하기 위해 부모 클래스의 소멸자를 가상 소멸자로 만들어 ( 앞에 virtual 을 붙여 ) 소멸자가 동적으로 바인딩 되게 해준다 .

가상 함수가 없는 클래스는 virtual 소멸자를 사용하지 말자 . virtual 함수를 가진 클래스들은 모두 별도로 4byte(32bit 에서 ) vptr 포인터를 가지므로 예상치 못한 작동을 하는 경우가 생길 수 있다 .

** 동적 바인딩 : 런타임에 자료형을 보고 virtual table(실행시켜야하는 함수 목록들이 있음 ) 을 참고하여 생성http://anster.egloos.com/2188896

** 추상 클래스인데 순수 가상함수를 만들 껀덕지가 없다면 소멸자에 쓰자 .

예외가 소멸자를 떠나지 못하도록 붙들어 놓자소멸자에서 예외가 빠져나가지 못하도록 삼키거나 프로그램을 중단 시키자 .

예외가 발생할 건덕지를 소멸자에 두어야 한다면 , 그 부분을 함수로 빼서 사용자에게 예외에 대처할 기회를 주자 .

객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 .

객체가 생성 중 일 때는 부모 클래스의 생성자가 먼저 실행되므로 자식 클래스의 데이터 멤버는 초기화되지 않은 상태이다 . 또한 자식 클래스 객체의 기본 클래스 부분이 생성되는 동안 그 객체의 타입은 기본 클래스이다 . 따라서 자식 클래스 생성자에서 가상 함수를 호출해봤자 부모 클래스의 함수가 호출된다 .

Page 12: 이펙티브 C++ 스터디

생성자 , 소멸자 , 및 대입 연산자

대입 연산자는 *this 의 참조자를 반환하게 하자대입 연산자가 올바르게 동작하는 것은 연산의 결과가 좌변 객체의 실체에 영향을 주기 때문이다 . 실체에 접근하는 관례적인 방법은 좌변 객체의 참조자를 반환하게 만드는 것이다 . 따라서 class A 의 대입 연산자 operator= 의 (= 이외에 +=, -=,*= 도 동일 ) 리턴타입은 A& 가 되고 리턴값은 *this 가 된다 .

operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자a=a 같이 바보 같아 보이는 코드도 흔히 있을 수 있다 .

operator= 을 쓸 때 자기자신이 대입되는 경우에는 복사 대상객체와 주소 비교 , new 보다 delete 를 늦게 하기 , “copy and swap” 기법 등을 통해서 문제없이 동작할 수 있도록 만들자 .

두 개 이상의 객체에 대해 동작하는 함수인 경우에는 같은 객체를 넘길 때 이상 없이 동작하는지를 체크하도록 하자 .

객체의 모든 부분을 빠짐없이 복사하자복사 생성자와 복사 대입 연산자를 통틀어 객체 복사 함수 (copying function) 라 부른다 .

자식 클래스의 복사 함수들을 만들 때 , 부모 클래스의 데이터들을 빼먹지 말자 . 부모클래스의 복사 생성자를 호출하고 복사 대입 연산자에서는 기본 클래스의 부분을 대입해주자 .

내용이 비슷하다고 해서 복사 함수들의 어느 한쪽을 이용해서 다른 쪽을 만들려는 시도는 하지 말자 . 제 3

의 함수 (init~) 를 만들고 이를 양쪽에서 각각 호출하는 방식으로 하자 .