Chapter 1. C++에 왔으면 C++의 법을 따릅니다. 항목 1 - 4

Effecive C++ 정리

Posted by Kyung Jun Cha on 2019-10-17

Chapter 1. C++ 에 왔으면 C++의 법을 따릅니다.


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

  1. 절차적(procedual) : C언어가 기본
  2. 객체 지향(object oriented) : 클래스, 캡슐화, 상속, 다형성, 가상함수 등 개념 사용
  3. 일반화(generic), 템플릿 메타프로그래밍
  4. STL, 템플릿 라이브러리

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

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

매크로 대신 상수를 사용하자

1
2
#define ASPECT_RATIO 1.653
const double AspetRatio = 1.653;

선행 처리자로 선언된 기호식 이름은 컴파일러 기호 테이블에 들어가지 않기 때문에 에러 발생 시 헷갈릴 수 있다.

주의점

상수 포인터를 정의하는 경우

const char * const authorName = "Scott Meyers";

클래스 멤버로 상수를 정의하는 경우 (컴파일러에서 상수 멤부 변수의 정의를 금지하는 경우)
상수의 정의을 구현 파일에 둔다.
1
2
3
4
5
6
7
8
class CostEstimate
{
private:
static const double FudgeFactor; //선언, 헤더 파일
...
};

const double CostEstimate::FudgeFactor = 1.35; // 정의, 구현 파일
나열자 둔갑술(enum hack)
1
2
3
4
5
6
class GamePlayer {
private:
enum { NumTurns = 5};

int scores[NumTurns]; //멤버 변수 초기화에 상수가 필요한 경우
};

매크로 함수보다 인라인 함수를 우선 생각하자

인라인 함수에 대한 템플릿을 준비하자

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

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

정의

상수 멤버 함수 : 해당 멤버 함수가 상수 객체에 대해 호출된 함수

1.  클래스의 인터페이스를 이해하기 좋게 한다.
2.  상수 객체를 사용할 수 있게 한다

비트수준상수성

멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 한다.

논리적 상수성

상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트는 정도는 바꿀 수 있되 그것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다.

  • mutable 사용 : 비정적 데이터 멤버를 상수 멤버 함수 안에서도 수정 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
class CTextBlock{
...
mutable std::size_t textlength;
...
};

std::size_t CTextBlock::length() const
{
...
textLength = std::strlen(pText);
...
}

상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법

  • 기능적으로 서로 똑같게 구현되어 있을 경우 비상수 버전이 상수 버전을 호출하도록
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TextBlock {
public:
...
const char& operator[](std::size_t position) const
{
...
return text[position];
}

char& operator[](std::size_t position)
{
return
const_cast<char&>( //const를 떄어냅니다.
static_const<const TextBlock&>(*this)[position]);_ //*this의 타입에 const를 붙여 op[]의 상수 버전을 호출합니다.
}
};
  • 앞의 방법을 뒤집어(상수 버전이 비상수 버전을 호출 하는 것) 하지 않는 이유는 상수 함수는 객체의 상태를 바꾸지 않겠다고 컴파일러와 약속한 함수이기 떄문에 배신하는 셈이 됩니다.

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

기본제공 타입의 객체는 직접 손으로 초기화합니다.

1
2
int x = 0;
const char * text = "A C-style string";

생성자에서 멤버 초기화 리스트를 사용하여 초기화 하고 리스트에 데이터 멤버를 나열할 떄는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열하자( 리스트 순서가 아닌 선언 순서로 초기화 되기 떄문에)

1
2
3
4
5
6
7
8
9
10
11
class ABC{
ABC(int x, std::string str);
private:
int number;
std::string name;
};

ABC::ABC(int x, std::string str)
: number(x),
name(str)
{}

비정역 정적 객체들의 초기화 순서는 보장되지 않는다. 해결책으로 비지역 정적 객체를 지역 정적 객체로 바꾼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FileSystem {...};

FileSystem& tfs()
{
static FileSystem fs;
return fs;
}

class Directory {...};

Directory::Directory( params )
{
...
std::size_t disks = tfs().numDisks(); //지역 정적 객체로 접근하지 않으면 초기화되지 않을 수 있어 안된다.
...
}

Directory& tempDir()
{
static Directory td;
return td;
}