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

y30n9ju1v·2021년 6월 6일
0

프로그래밍

목록 보기
3/18

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

초창기의 C++은 단순히 C언어에 객체 지향 기능 몇 가지가 결합된 형태
초창기 이름도 클래스를 쓰는 C(C with Classes)
꾸준히 성장한 C++은 다중 패러다임 프로그래밍 언어가 됨
C++을 잘 이해하기 위해서는 한 가지 프로그래밍 규칙 아래 뭉친 통합 언어가 아니라 4가지 하위 언어들의 연합체로 이해하자

  1. C
    여전히 기본으로 하는 C 부분
  2. 객체 지향 개념의 C++
    클래스, 캡슐화, 상속, 다형성, 가상 함수 등 객체 지향 부분
  3. 템플릿 C++
    C++의 일반화 프로그래밍 부분
  4. STL(Standard Template Library)
    컨테이너, 반복자, 알고리즘과 함수 객체를 포함하는 템플릿 라이브러리 부분

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

"가급적 선행 처리자보다 컴파일러를 더 가까이 하자"

1. const 사용

#define ASPECT_RATIO 1.653

이 코드는 컴파일러에게 넘어가기 전에 선행 처리자가 ASPECT_RATIO를 숫자로 바꿈
상수로 대체된 코드에서 컴파일 에러가 발생하면 원인을 꽤나 헷갈릴 수 있음
해결법은 매크로 대신 상수를 사용

const double AspectRatio = 1.653;

AspectRatio는 언어 차원에서 지원하는 상수 타입이기 때문에 기호 테이블에 들어가 디버깅하기 편함
매크로를 쓰면 ASPECT_RATIO가 등장하기만 하면 선행 처리자가 모두 1.653으로 바꾸면서 목적 코드 안에 1.653의 사본이 등장 횟수만큼 들어가기 때문에 최종 크기가 큼
상수 타입의 AspectRatio는 여러 번 쓰더라도 사본은 한 개만 생기기 때문에 최종 크기가 작음

2. enum 사용

클래스 컴파일 도중에 상수의 값이 필요할 경우 '나열자 둔갑술(enum hack)'을 사용하자
enum은 #define처럼 어떤 형태의 메모리 할당을 저지르지 않음

class temp
{
	const int a = 10; // error
    	enum {a = 10}; // ok
    	int data[a];
};

3. inline 사용

함수 매크로 대신에 정규 함수의 모든 동작 방식 및 타입 안전성까지 취할 수 있는 inline template 함수를 사용하자

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

위 코드는 아래의 경우 예상치 못한 동작을 함

int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a is incremented twice

그래서 아래와 같이 inline template 함수를 사용하는 것이 훨씬 안전함

template<typename T>
inline void callWithMax(const T& a, const T& b)
{
	f((a > b) ? a : b);
}

Item 3. 낌새만 보이면 const를 들이대 보자!

const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는데 도움을 줌
아래 코드에서 상수를 수정하려고 한다면 컴파일러가 에러를 발생함

char greeting[] = "Hello";

char *p = greeting;			// 비상수 포인터, 비상수 데이터
const char *p = greeting;		// 비상수 포인터, 상수 데이터 
char * const p = greeting;		// 상수 포인터, 비상수 데이터
const char * const p = greeting;	// 상수 포인터, 상수 데이터

컴파일러 쪽에서 보면 비트 수준 상수 성을 지켜야 하지만 프로그래머는 개념적인(논리적인) 상수 성을 지키며 프로그래밍해야 함

  • 비트 수준 상수 성은 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 함
  • 논리적 상수성은 상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자 측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것
class temp 
{
	char& operator[](size_t p) const
    	{
    		return pText[p];
    	}
private:
	char *pText;
};

const temp t("Hello");
char *pc = &t[0];
*pc = 'h';

위 코드를 보면 operator[]함수는 상수 멤버 함수로 비트수준 상수성을 만족시켜 컴파일러는 에러를 발생하지 않음
하지만 그 주소를 이용해 데이터 수정이 가능하기 때문에 프로그래머는 논리적 상수성을 지키며 프로그래밍 해야 함

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

  1. 기본 제공 타입의 객체는 직접 손으로 초기화 하자.
    경우에 따라 저절로 되기도 하고 안되기도 하기 때문

  2. 생성자에서는 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 사용하자.
    기본 생성자 호출 후에 복사 대입 연산자를 연달아 호출하는 방법보다 복사 생성자를 한번 호출하는 쪽이 더 효율적이기 때문

  3. 서로 다른 소스 코드에 정의된 정적 객체들 사이의 상대적인 초기화 순서는 정해져 있지 않음을 항상 염두해 두자.

0개의 댓글