Item 1. View C++ as a federation of languages
오늘날의 C++는 다중 패러다임 프로그래밍 언어(multiparadigm programming language)로 여러 sublanguage들의 연합체다. 따라서, C++를 사용한 효과적인 프로그래밍 규칙은 C++의 어떤 부분을 사용하느냐에 따라 달라진다.
1) C
C++는 C를 기반으로 함.
Blocks, statements, the preprocessor, built-in data types, arrays, pointers 등이 C에서 왔다.
C만 써도 문제는 없으나 활용할 수 있는 범위가 좁아진다 : no templates, no exceptions, no overloading, ...
2) Object-Oriented C++
객체 지향 설계를 위한 규칙이 가장 직접적으로 적용되는 부분이다.
classes(constructors, destructors포함), encapsulation, inheritance, polymorphism, virtual functions(dynamic binding), etc.
3) Template C++
C++의 generic programming 부분
오늘날 프로그래밍 언어들은 저마다 Template 구문을 가지고있다.
Template은 매우 강력하며 template metaprogramming(TMP)라는 완전히 새로운 프로그래밍 패러다임을 발생시켰다.
4) The STL
특별한 template library.
containers, iterators, algorithms, function object들이 서로 맞물려 작동한다.
템플릿과 라이브러리는 얼마든지 다른 아이디어를 중심으로 만들어질 수 있다.
STL에는 특정한 작업 방법이 있으며, 작업시 규칙을 반드시 따라야 한다.
- 한 sublanguage에서 다른 sublanguage로 변경시, 전략의 변화가 필요하다.
C의 built-in-types: pass by value가 효과적이다.
Object-Oriented C++의 object: 사용자가 정의한 생성자와 소멸자가 존재하기 때문에 pass by reference to const를 사용한다.
Template C++: 우리가 다루는 type에 대해 알지 못하기 때문에 pass by reference to const를 사용한다.
STL: iterators와 function object들이 C의 포인터를 기반으로 하기 때문에 pass by value를 적용한다.
Item 2. Prefer consts, enums, and inlines to #defines
전처리기(preprocessor)보다 컴파일러(compiler)를 선호하자.
#define을 통해 ASPECT_RATIO 매크로를 만드는 경우를 예로 들어보자.
#define ASPECT_RATIO 1.653
ASPECT_RATIO라는 이름은 컴파일러는 절대 볼 수 없다.
컴파일러가 소스 코드를 가지기 전에 전처리기가 ASPECT_RATIO를 제거하기 때문에, 결과적으로 symblo table에 이름 ASPECT_RATIO가 포함되지 않는다.
따라서, 컴파일 도중 에러가 발생하면 에러 메시지는 ASPECT_RATIO가 아닌, 1.653을 가리킬 것이다.
만약 ASPECT_RATIO가 자신이 작성하지 않은 헤더파일에 정의되어 있다면, 1.653이 어디에서 왔는지 알 수 없고, 디버깅에 시간을 낭비할 것이다.
해결방안은 이것을 constant로 대체하는 것이다.
const double AspectRatio = 1.653;
AspectRatio는 컴파일러에 의해 보여지며 symbol table에 포함된다.
게다가, constant AspectRatio는 두번 이상의 복사를 초래하지 않지만, 매크로는 object code에 다수의 1.653에 대해 복사를 수행하기 때문에 constant를 사용하면 코드의 크기가 작아진다.
#define을 constant로 대체하는 경우, 두 가지 특별한 케이스가 존재한다.
1. Defining constant pointers
const char*-based string을 헤더파일에 정의하기 위해서, const를 두번 사용해야 한다.
일반적으로 char*-based string대신 string 객체가 선호된다.
const char* const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers");
2. Class-specific constant
constant의 scope를 클래스로 한정하기 위해서, constant를 클래스의 멤버로 만들고 최대 하나의 constant 복사본이 있는지 확인해야 하며, static member로 만들어야 한다.
class-specific constant이고 그 값이 static이고 integral type(e.g., int, chars, bools)이면, 정의를 할 필요없이 선언을 하는 것 만으로도 값을 사용할 수 있다.
만약, constant의 주소를 가져야 하거나, 컴파일러가 정의를 잘못 주장한다면, 따로 정의를 제공해야 하며 이것은 implementation file에 넣는다.
class constant의 초기값은 constant가 선언된 위체에 제공되기 때문에, 정의 시점에서 초기값은 허용되지 않는다.
class GamePlayer{
private:
static const int NumTurns = 5;
int scores[NumTurns];
};
const int GamePlayer::NumTurns;
그러나, older compiler는 위의 문법을 허용하지 않기 때문에 static class member의 초기값을 선언시 제공할 수 없다.
게다가 in-class initialization은 오직 integral type과 constant에 대해서만 허용된다.
이 경우에는 값을 정의할 때 초기값을 준다.
class CostEstimate{
private:
static const double FudgeFactor;
};
const double ConstEstimate::FudgeFactor = 1.35;
3. Enum Hack
static integral class constant에 대한 클래스 내 초기 값 지정을 금지하는 컴파일러에 대해, constant를 declaration of the array에 사용할 수 없다.
이 경우 'enum hack'이라는 방법을 사용한다.
int가 쓰여져야 하는 곳에 enumerated type의 값을 사용할 수 있다는 장점을 가진다.
enum hack은 const보다 #define에 가깝게 작동한다.
const의 주소값을 가져올 수 있지만, enum과 #define의 주소를 가져올 수 없다.
따라서 integral constant에 대한 포인터나 레퍼런스를 가지는 것을 허용하고 싶지 않다면, enum을 사용하는 것이 좋다.
enum hack은 순전히 실용적이다.
많은 코드가 그것을 사용하고, template metaprogramming의 필수적인 기술이다.
4. Function-like macros, inline function
#define을 사용하는 것 보다는 inline function을 사용하는 것이 선호된다.
Item 3 : Use const whenever possible
Const는 특정한 객체가 수정되지 않도록 컴파일러에게 강제한다.
char greeting[] = "Hello";
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
class CostEstimate{
private:
static const double FudgeFactor;
};
const double ConstEstimate::FudgeFactor = 1.35;
const가 asterisk(별표) 왼쪽에 나타나면, pointer가 가리키는 객체가 constant임을 의미하며, 오른쪽에 나타나면 pointer 자체가 constant임을 의미한다.
const가 두 곳에서 나타나면, pointer가 가리키는 객체와 pointer가 모두 constant이다.
1. STL interators
STL iterators는 pointer를 기반으로 두기 때문에, iterator는 T* pointer처럼 동작한다.
따라서 iterators를 const로 선언하는 것은 pointer를 const로 선언하는 것과 같으며, 이 경우 iterator는 다른 값을 가리킬 수 없다.
만약 iterator가 가리키는 것을 수정할 수 없도록 만들고자 한다면, const_iterator를 사용한다.
std::vector<int> vec;
// 1. const pointer
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;
++iter; // error! iter is const
// 2. const data
std::vector<int>::const_iterator iter = vec.begin();
*iter = 10; // error! *iter is const
++iter;
2. Function declarations
함수 선언에서 const는 함수의 return value, individual parameter를 가리킬 수 있으며 member function, function as a whole에서 사용할 수 있다.
함수의 리턴 값을 const로 선언하면, 반환한 값이 의도하지 않게 수정되는 것을 막을 수 있다.
parameter또는 local object를 수정할 필요가 없다면, const로 선언하는 것이 좋다.
3. Const member functions
const를 멤버 함수에 사용하는 목적은 어떤 멤버 함수가 const 객체에 의해 호출될 수 있는지 확인하는 것이다.
const 멤버 함수는 클래스 인터페이스를 쉽게 이해할 수 있고, const 객체에 대해 작동할 수 있도록 하기 때문에 중요하다.
const만 다른 함수는 오버로드될 수 있다.
class TextBlock{
public:
const char& operator[](std::size_t position)const
{return text[position];}
char& operator[](std::size_t position)
{return text[position];}
private:
std::string text;
};
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
일반적으로 알려진 const는 bitwise constness(physical constness)와 logical constness가 있다.
bitwise constness는 객체의 어떤 멤버 데이터도 변경되지 않는 경우에만 멤버 함수가 const라고 한다.
즉, 객체 안의 어떤 bit도 수정하지 않는다.
bitwise constness는 위반을 감지하기 쉬우며, C++의 constness의 정의로 사용된다.
logical constness는 const member function이 호출될 때 객체의 어떤 bit도 수정되지 않을 것이라고 주장하는 것이다.
4. Mutable
mutable은 non-static data members를 bitwise constness의 제약으로부터 자유롭게 한다.
즉, mutable로 선언된 data member는 const 함수에서도 값을 변경할 수 있다.
5. Avoid Duplication in const and Non-const Member Function
const와 const가 아닌 멤버 함수를 작성하면 code duplication(코드 중복)이 발생한다.
실제로 원하는 것은 함수를 한번만 작성하고 그것을 두번 사용하는 것이다.
만약 const와 non-const 멤버 함수의 구현이 동일하다면, non-const 함수에서 const 버전을 호출하도록 하여 코드 중복을 피할 수 있다.
class TextBlock{
public:
const char& operator[](std::size_t position)const
{
return text[position];
}
char& operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
};
정리하면, Const로 선언하는 것은 컴파일러가 usage error를 감지하는데 도움이된다.
const는 객체의 어떤 scope에서도 적용될 수 있다(예를 들어, function parameter, return types, and member functionsa as a whole).
컴파일러는 bitwise constness를 적용하지만, 개념적 constness를 사용하여 프로그래밍해야 한다.
const와 non-const 멤버 함수가 본질적으로 동일한 구현을 가질 때, non-const버전이 const버전을 호출하도록 하여 코드 중복을 피할 수 있다.
Item 4 : Make sure that objects are initalized before they're used
Built-in type의 객체에 대해 수동으로 초기화를 해야 한다.
생성자에서, 생성자 본문 내에서 할당하는 것보다 멤버 초기화 목록을 사용하는 것이 선호된다.
초기화 리스트에서 멤버 데이터를 나열하는 것은 클래스에서 선언된 것과 같은 순서로 나열되어야 한다.
로컬이 아닌 정적 개체를 로컬 정적 개체로 교체하여 translation units(번역 단위 전체)에서 초기화 순서 문제를 방지할 수 있다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] Chapter 4: Design and Declarations(2) (0) | 2022.07.05 |
---|---|
[Effective C++] Chapter 4: Design and Declarations(1) (0) | 2022.07.02 |
[Effective C++] Chapter 3: Resource Management (0) | 2022.07.01 |
[Effective C++] Chapter 2: Constructors, Destructors, and Assignment Operators(2) (0) | 2022.06.30 |
[Effective C++] Chapter 2: Constructors, Destructors, and Assignment Operators(1) (0) | 2022.06.30 |