Effective C++ Summary - Chap 01.

Chap 1. C++에 왔으면 C++의 법을 따릅시다.

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

C++는 한 가지 프로그래밍 규칙 아래 구성된 통합언어(unified language)가 아니라 네 가지 하위 언어들의 연합체(federation) 라고 보는 것이 언어를 이해하기에 좋다.

  1. C
    • 블록, 문장, 선행 처리자, 기본제공 데이터타입, 배열, 포인터 etc…
  2. OOP로써의 C++
    • 클래스를 사용하는 C : 클래스 , 캡슐화, 상속, 다형성, 가상 함수 etc
  3. 템플릿 C++
    • Template Meta-programming : TMP
  4. STL
    • 템플릿 라이브러리 : container / iterator / algorithm / function object

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

매크로인 #define를 수식이나 상수에 사용하는 것은 떄때로 예측할 수 없는 결과를 가져올 수 있으며 , 디버깅에 있어서 비효율적이다. 일례로 아래와 같은 컴파일 에러를 생각해 보자.

1
2
3
// Bad Example
#define ASPECT_RATIO 1.563
int ratio = ASPECT_RATIO / 0;

컴파일러로 코드가 넘어간 후에는ASPECT_RATIO가 symbolic name으로 남지 않고,숫자 상수로 대체되어 버린다. 때문에 컴파일러의 기호 테이블에 들어가 있기 때문에 컴파일 에러라도 나면, 복잡한 코드에서는 이를 바로잡기가 쉽지 않다. 특히나 매크로 함수는 큰 재앙이 될 수도 있다.

1
2
3
4
5
// Bad Example
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
int a=5, b=0;
CALL_WITH_MAX(++a, b);
CALL_WITH_MAX(++a, b + 10);

위와 함수의 경우 a는 7이 되고, 아래의 경우는 6이 되어버린다. 의도되지 않은 결과가 나와버린다.

상수를 선언하는 올바른 코드는 아래와 같다.

1
2
// Good Example
const double AspectRatio = 1.653;

클래스 안에서 상수를 사용하고 싶을 때는 static const 혹은 enum을 사용하자.

1
2
3
4
5
6
// Good Example
class ConstEstimate{
private:
static const double FudgeFactor = 1.35;
enum { NumTurns = 5};
}

매크로 함수의 경우, template function을 사용하면 매크로의 효율적인 코드의 측면을 가져갈 수 있다.

1
2
3
4
5
6
// Good Example
template<typename T>
inline void callWithMax (const T& a, const T& b)
{
f(a > b ? a : b);
}

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

const는 컴파일러가 해당 변수 값에 대한 제약을 단단히 지켜준다는 점에서 강력하고 매력적인 도구이다. 다양한 const의 사용 예를 분석해 보자.

1
2
3
4
char *p = greeting; // non-const pointer, non-const data
const char *p = greeting; // non-const pointer, const data
char *const p = greeting; // const pointer, non-const data
const char *const p = greeting // const pointer, const data

규칙이 조금은 헷갈리긴 하지만 표시를 기준으로, 왼쪽에 const는 data를 상수로 취취한다. 반면 오른쪽에 있는 const는 포인터 자체를 상수로 취하게 된다. 이는 다음 예를 통해 좀 더 명확히 구분이 가능하다.

1
2
void f1(const int *pw); // non-const pointer, const data
void f2(int const *pw); // non-const pointer, const data

위의 두 가지 경우에 대해서 모두 가능한 문법이며, 둘 다 int 형의 data가 상수임을 의미한다. f1 방식이 일반적이긴 하나, 둘 다 옳은 표현식이므로 기억해두자.


상수 객체를 생성할 경우에는 그에 맞추어 상수 멤버함수도 정의해줄 필요가 있다. 다음 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
class TextBlock{
public:
const char& operator[](std::size_t pos) const
{ return text[position];}
char& operator[](std::size_t pos)
{ return text[position];}
}
int main(){
TextBlock tb("Hello");
std::cout << tb[0];
const TextBlock ctb("World");
std::cout << ctb[0];
return 0;
}

const char& operator[] (std::size_t pos) const
에서 중요한 점은 앞의 const는 return 값인 char&가 상수임을 뜻한다. 반면 뒤의 const는 이 멤버 함수가 상수형으로 정의되었으며, 이는 상수 객체에 대해서 적용되는 함수임을 나타내는 부분이다.

  • 추가적으로 공부가 더 필요한 부분
    • 비트수준 상수성(bitwise constness) / 논리적 상수성(logical constness)
    • const_cast, static_cast의 사용 방법 및 사용 예시 이해

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

초기화 되지 않은 채 생성된 변수들은 어디에선가 큰 문제를 일으킬 소지가 다분하다. 정의되지 않은 동작이 발생할 가능성이 높기 때문이다. 최악의 경우에는 어떤 플랫폼에서 미초기화 객체를 읽기만 해도 프로그램이 다운되기도 한다. 디버깅을 최대한 줄이기 위해서는 초기화를 하는 것이 효과적이다.

  1. built-in type의 객체들은 직접 손으로 초기화하자.
  2. 생성자에서는 멤버 초기화 리스트를 사용하자.
  3. non-local한 객체는 local하게 바꾸어 사용하자. (non-local한 객체를 만들면 translation unit 단계에서 정의되지 않은 동작이 일어날 수 있다.)
  • Bad Example
    global한 변수인 tfs가 중간에 컴파일러 상 어떤 멤버 초기화 과정을 뒤죽박죽으로 할 지 알 수 없어서 위험하다.
1
2
3
4
5
6
7
class FileSystem{
public:
std::size_t numDisks() const;
...
}
extern FileSystem tfs;
std::sizet disks = tfs.numDisks(); // bad Example
  • Good Example
    tfs()가 local하게 선언되어 있으며, 이는 C++ 컴파일러상 반드시 멤버 초기화 과정을 거치므로 안전하다.
1
2
3
4
5
6
7
8
9
10
class FileSystem{
public:
std::size_t numDisks() const;
...
}
FileSystem& tfs(){
static FileSYstem fs;
return fs;
}
std::sizet disks = tfs().numDisks(); // good example
Share Comments