본문 바로가기

C, C++/Effective C++

1장 C++과 친해지기 (1)

Item1: C++을 언어의 연합(federation) 으로 보기

개요

현대 C++은 Procedural, Object-Oriented, Functional, Generic, Metaprogramming 같은 프로그래밍 패러다임이 혼합되어 있다.

 

이는 유연함을 주면서 혼동을 주기도 한다.

따라서 C++을 하나의 언어가 아니라 여러 개의 관련 언어가 연합된 형태로 바라보는 것이 이해하기 쉽다.

C++의 서브 언어는 4가지 분류가 있다.

 

 

서브 언어

1. C스타일의 C++

  • C에서 사용하던 블록 구조, 문장, 전처리기, 기본, 데이터 타입, 배열, 포인터 등을 그대로 사용한다.
  • C++은 C보다 더 나은 해결책을 제시한다. (뒤에 나온다고 함)

2. 객체지향 C++

  • 클래스, 생성자, 소멸자, 캡슐화, 상속, 다형성, 가상 함수(동적 바인딩) 등의 개념 포함

3. 템플릿 기반 C++

 

4. STL: STL은 특수한 템플릿 라이브러리이다. STL만의 독특한 규칙을 따르는 것이 중요하다.

서브언어 주요 개념 사용 예시
C 스타일 C++ 기본 데이터 타입, 포인터, printf() char* 기반 문자열 처리, C 함수 사용
객체지향 C++ 클래스, 상속, 다형성, 가상 함수 class, public/private, override
템플릿 C++ 제너릭 프로그래밍, 템플릿 메타프로그래밍 template <typename T>, TMP 활용
STL 컨테이너, 알고리즘, 이터레이터 std::vector, std::sort(), std::map

 

 

각 서브 언어를 선택하는 전략

예를 들어 

  • C 스타일 C++에서는 pass by value가 내장 타입에서 더 효율적이다.
  • 객체지향 C++에서는 생성자, 소멸자로 인해 pass by reference to const가 더 낫다.
  • 템플릿 C++에서는 객체의 타입을 바로 알 수 없어 pass by reference to const가 더 중요해진다.
  • STL의 iterator, functor는 C스타일 포인터 기반으로 동작해, pass by value가 적절한 경우가 많다.

결론: 효율적인 C++ 프로그래밍 규칙은 C++의 어느 부분을 사용하는 지에 따라 달라진다.(특정 규칙을 맹목적으로 적용하지 말고 상황에 맞게 판단하자)

 

 

 

Item2: #defines대신 const, enum, inline 쓰기

#define의 문제

1. #define은 전처리기가 값을 치환하고 컴파일러로 전달하기 때문에 컴파일러는 기존 선언 이름을 모른다.

#define ASPECT_RATIO 1.653

 

ASPECT_RATIO라는 이름을 모른다.

 

2. #define으로 정의된 값은 symbol table에 들어가지 않아 디버거에서 값의 출처를 알 수 없다.

 

3. 컴파일된 바이너리에서 여러 개의 치환된 값이 존재할 수 있다.

 

 

따라서 const를 사용하자!

컴파일러가 값을 symbol table에 저장하게되어 디버깅이 쉬워진다.

 

 

#define -> const에서 주의할 점

1. 상수를 가리키는 포인터 정의 시

const char * const authorName = "Scott Meyers";

 

포인터가 가리키는 값과 포인터 자체를 모두 const로 선언한다. 

첫 번째 const -> 포인터가 가리키는 값

두 번째 const -> 포인터 자체

물론 char*를 string으로 대체 하는것이 좋다.

 

2. 클래스 내부에서 사용되는 상수

class GamePlayer {
private:
    static const int NumTurns = 5;  // 상수 선언
    int scores[NumTurns];           // 상수 사용
    ...
};

static 으로 사용 시 객체를 생성할 때마다 만들어지는 것이 아니라, 클래스 전체에서 단 하나의 복사본을 유지한다.

 

 

클래스 상수는 #define 불가능!

#define은 scope개념이 없어서 클래스 내부에서 한정적으로 사용하는 것이 불가능하다.

 

 

Enum Hack

static const int 멤버 초기화 시에, 일부 컴파일러에서 오류가 발생할 수 있다고 한다.

이유는 static const int값을 컴파일 타임 상수로 처리하지 못해서다.

이를 위해 enum으로 우회하는 방법을 사용한다고 한다.

class GamePlayer {
private:
    enum { NumTurns = 5 };  // "Enum Hack" - NumTurns를 5의 심볼릭 이름으로 만듦
    int scores[NumTurns];   // 정상적으로 컴파일됨
};

 

Enum Hack은?

  • 메모리를 차지하지 않는다.
  • #define 보다 안전하다.
  • 참조를 얻을 수 없다.
  • 템플릿 메타프로그래밍에서 유용하다.

 

최신 C++에서는(C++11이상) constexpr이 도입되어 Enum Hack 안써도 됨

 

 

#define을 사용한 매크로 기반 함수의 문제점

1.연산자 우선순위 문제: 전처리기 매크로는 단순한 텍스트 치환이기 때문에 연산자 우선순위 문제가 생긴다.

#define SQUARE(x) x * x
int a = 5;
int b = SQUARE(a + 1); // 기대값: (6 * 6) = 36

 

SQUARE(a + 1)는 a + 1 * a + 1로 치환되어 6 + 6 + 1 = 13이 된다.

 

 

2. Multiple Evaluation

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);   // a가 두 번 증가됨!
CALL_WITH_MAX(++a, b+10); // a가 한 번만 증가됨

 

a가 ++a가 되기 때문에 만약 a가 더 큰 경우 a가 두 번 증가한다.

 

 

인라인 함수를 통한 해결

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

 

인라인 함수는 타입체크가 가능하고 단순 치환이 아니기 때문에 #define에 완전한 대체재이다.