공부 기록

[Effective C++] Ch 1. C++에 왔으면 C++의 법을 따릅시다 본문

Language/C++

[Effective C++] Ch 1. C++에 왔으면 C++의 법을 따릅시다

혜멘 2022. 5. 12. 13:02

항목1) C++를 언어들의 연합체로 바라보는 안목은 필수

C++란? 절차적 프로그래밍을 기본으로 하여 객체지향, 함수식, 일반화 프로그래밍을 포함하는 메타 프로그래밍 언어

이를 단일 언어로 바라보기보다 상관관계가 있는 여러 언어들의 연합체로 바라보면 더욱 명확해진다.

C++을 사용한 효과적인 프로그래밍 규칙은 C++의 어느 부분을 사용하느냐에 따라 달라진다.

 

1) C

- 블록, 문장, 선행처리자, 기본 제공 데이터타입, 배열, 포인터 등의 C의 모든 것

- C의 기능에 대응되며 이보다 월등한 C++ 기능이 제공되고 있긴 함 (항목2, 항목13 참조)

 

2) 객체지향 개념의 C++

- 클래스를 쓰는 C

- 캡슐화, 상속, 다형성, 가상함수(동적 바인딩) 등의 개념이 포함되며 객체 지향 설계의 규칙 대부분이 적용됨

 

3) 템플릿 C++

- 일반화 프로그래밍 부분

- TMP (뎀플릿 메타 프로그래밍) 이라는 패러다임이 파생되기도 함, 주류 C++과는 조금 다름 (항목 48 참조)

 

4) STL

- 표준 템플릿 라이브러리

- 컨테이너, 반복자, 함수객체, 알고리즘 등이 얽혀 돌아감

- STL 만의 사용규약을 따라 프로그래밍 하면 됨

 

항목2) #define을 쓰려거든 const, enum, inline을 떠올리자

가급적 선행 처리자보다 컴파일러를 더 가까이 하자. (빌드 순서 : 선행처리자→컴파일러링커)

 

#define을 사용하면 소스코드가 컴파일러에 넘어가기 전에 숫자 상수로 대체되므로 디버깅 시 시간이 소모됨.

#define은 매크로가 사용된 개수만큼 사본이 만들어지지만, const는 상수객체 한 개의 사본만 만들어지므로 효율적.

상수 포인터를 const로 정의할 경우 포인터와 포인터가 가리키는 대상까지 const로 선언해주자. 

 

const는 선언 시점에 초기값을 주는 것이 맞지만, 오래된 컴파일러의 경우 정적 클래스 상수의 선언시점에 초기값을 주는 것이 틀렸다고 할 때가 있음. 이런 경우 선언을 헤더파일에 두고 정의(초기화)는 구현파일에 두도록 한다.

→ 그러나 클래스 선언 시점에 클래스 상수 값을 사용해야 한다면, Enum Hack 기법을 사용하도록 하자

 

Enum Hack : 나열자 둔갑술

- 사용하고 싶은 상수객체를 enum의 기호식 이름으로 사용 ( enum { Num = 5 } )

- 그대로 다른 클래스 멤버 선언에 사용하면 됨

- enum은 주소를 취할 수 없으므로 const 보다는 #define에 가까운 동작 방식

- 템플릿 메타 프로그래밍의 핵심 기법 (항목 48 참조) 

 

항목3) 낌새만 보이면 const를 들이대보자

const 키워드가 붙은 객체는 변경이 불가능하여 컴파일러가 사용상의 에러를 잡아내는데 도움을 준다.

어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환 타입, 멤버 함수에도 붙을 수 있다.

멤버함수의 반환 값에 const가 붙으면 상수 객체를 반환한다는 뜻이고, 선언부 마지막에 붙으면 이 함수에서는 어떤 객체의 값도 변경하지 않음을 뜻한다.

 

비트수준 상수성

- 말그대로, 메모리 상에 저장된 비트의 값이 변하지 않는 것

- 컴파일러에서는 비트수준 상수성만을 체크, 프로그래밍 할 때는 논리적 상수성을 고려하며 짜야 함

ex) 외부에 참조자를 전달하기만 하는 함수의 경우 함수 내부는 비트수준 상수성이 유지되어 컴파일에 문제가 없음,

그러나 함수 외부에서 반환 받은 참조자를 통해 멤버 데이터의 값을 변경시켜 논리적으로 상수성이 지켜지지 않게 됨

 

상수멤버 및 비상수멤버 함수의 코드중복 피하기

- 비상수와 상수함수가 반환값만 다르고 내용이 똑같은 경우, 비상수함수에서 상수함수를 호출한다.

- 호출된 값에서 const cast를 사용해 상수성을 날려 비상수 값을 얻을 수 있음. 단, 반대는 불가능

 

포인터에 const를 붙일때

- const가 *의 왼쪽에 오면 데이터가 상수, 오른쪽에 오면 포인터가 상수

- const char *p : 상수 데이터

- char * const p : 상수 포인터

 

STL 반복자

- 포인터와 비슷하게 동작

- const_iterator : 상수 데이터

- const vector<int>::iterator : 상수 반복자

 

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

대입과 초기화를 구분하고 객체 생성 시 초기화 리스트를 사용하자.

기본 클래스는 파생 클래스보다 먼저 초기화되고, 클래스 데이터 멤버는 선언 순서대로 초기화된다.

따라서 불필요한 버그를 막으려면 초기화 리스트의 나열 순서도 선언 순서대로 하는 것이 좋다.

 

초기화 리스트란?

- 복사 생성자를 이용한 초기화

- 생성자 본문에서 복사 대입 연산자를 사용하는 것 보다 훨씬 효율적

- 상수와 참조자 멤버가 있는 경우 반드시 초기화 리스트를 통한 초기화가 필요함 (대입이 불가능)

- int 등의 기본 제공 타입은 초기화와 대입의 비용 차이가 없지만, 초기화 리스트에 넣어주는게 좋다

 

비지역 정적 객체의 초기화 순서는 개별 번역 단위에서 정해지며, 상대적인 순서는 지정되어 있지 않다.

그러나 지역 정적 객체는 함수가 처음 호출된 시점에 초기화가 이루어진다.

따라서 정적 객체의 초기화에 다른 정적 객체가 필요하다면 해당 객체를 지역 정적 객체로 사용해 초기화 문제를 피한다.

싱글턴 패턴의 전형적인 구현 양식!

 

정적 객체의 종류

- 전역 객체

- 네임스페이스, 파일 등의 유효범위에서 정의된 객체

- 클래스에서 static으로 정의된 객체

- 함수에서 static으로 정의된 객체 → 지역 정적 객체

 

번역단위란?

- 컴파일을 통해 하나의 오브젝트 파일을 만들 수 있는 소스코드

- 기본적으로 소스 파일 한 개 단위 + 그 파일이 include하는 다른 파일들

 

 

Comments