[Effective C++ 3판] Chapter 1. C++에 왔으면 C++의 법을 따릅시다. (항목 1~4)
앞으로 Chapter별로 포스팅을 할 계획이다.
책의 내용을 완벽하게 정리하려는게 목적은 아니다.
내가 잘 몰랐던 것과 헷갈리는 것을 위주로 해서 개인적인 생각과 궁금한 것에 대한 추가적인 실험/연구 결과를 정리해나갈 것이다.
- const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다.
- 컴파일러 쪽에서는 비트수준 상수성을 지켜야 하지만, 우리는 논리적 상수성을 사용해서 프로그래밍해야한다. (mutable keyword)
- 상수 멤버와 비상수 멤버 함수이 똑같을 경우엔 비상수버전이 상수버전을 호출함으로서 코드 중복을 피할 수 있다.
[ 정리해 두고 싶은 것 & 추가적인 사항 ]
- 객체 멤버 변수는 선언된 순서대로 초기화 된다. (초기화 리스트에서의 순서는 상관X)
- 생성자 본문에서 초기화를 하면 그건 그냥 이미 초기화된 변수에 대입을 하는 셈. (생성자 본문이 실행되기전에 초기화는 완료된다.) 그래서 반드시 초기화 리스트를 사용해야 하는 것.
책의 내용을 완벽하게 정리하려는게 목적은 아니다.
내가 잘 몰랐던 것과 헷갈리는 것을 위주로 해서 개인적인 생각과 궁금한 것에 대한 추가적인 실험/연구 결과를 정리해나갈 것이다.
Chapter 1. C++에 왔으면 C++의 법을 따릅시다.
항목 1 : C++를 언어들의 연합체로 바라보는 안목은 필수.
[ 이것만은 잊지 말자! ]
- C++을 사용한 효과적인 프로그래밍 규칙은 경우에 따라 달라집니다. 그 경우란, 바로 C++의 어떤 부분을 사용하느냐입니다.
항목 2 : #define을 쓰려거든 const, enum, inline을 떠올리자.
#define에게는 유효범위라는 게 없다. 이 점 때문에 외부 라이브러리와 #define이 겹쳐서 고생한 기억이 여러 번 있다. (부들부들..) 그리고 #define은 그냥 단순 치환방식이기 때문에 매우 불안하다. 따라서 상수를 정의할 때는 const 변수를 사용하는 것이 좋다. 혹은 정수 타입에 해당하는 상수를 위해서 나열자 둔갑술 (enum hack)을 사용하는 방법도 있다. 나열자 둔갑술은 단순히 enum { ... }; 이런식으로 선언함으로서 해당 namespace에 한해서 정수형 상수를 정의할 수 있는 방법이다.
[ 정리해 두고 싶은 것 & 추가적인 사항 ]
- 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 '정의'가 없어도 된다.
[ 이것만은 잊지 말자! ]
- 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각합시다.
- 함수처럼 쓰이는 매크로를 만들려면 #define 매크로보다 인라인 함수를 우선 생각합시다.
항목 3 : 낌새만 보이면 const를 들이대 보자!
[ 정리해 두고 싶은 것 & 추가적인 사항 ]// 컴파일러는 비트수준 상수성만 보장해준다.
// 우리는 논리적 상수성을 고려해서 프로그래밍해야한다.
class Example {
private:
int *data;
int num;
mutable int callCnt;
public:
int* getData() const {
// It is valid in bitwise constness, but invalid in logical constness.
return data; // compile ok.
}
int& getData(int idx) const {
// It is valid in bitwise constness, but invalid in logical constness.
return data[idx]; // compile ok.
}
int& getNum() const {
// It is both invalid in bitwise constness and logical constness.
return num; // compile error.
}
void call() const {
// It is invalid in bitwise constness, but valid in logical constness.
++callCnt; // compile ok.
}
};
// 상수 멤버와 비상수 멤버 함수이 똑같을 경우엔
// 비상수버전이 상수버전을 호출함으로서 코드 중복을 피할 수 있다.
class Example {
private:
Data *data;
public:
const Data& operator[](int idx) const {
return data[idx];
}
Data& operator[](int idx) {
return
const_cast<Data&>(
static_cast<const Example&>(*this)[idx]
);
}
};
[ 이것만은 잊지 말자! ]- const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다.
- 컴파일러 쪽에서는 비트수준 상수성을 지켜야 하지만, 우리는 논리적 상수성을 사용해서 프로그래밍해야한다. (mutable keyword)
- 상수 멤버와 비상수 멤버 함수이 똑같을 경우엔 비상수버전이 상수버전을 호출함으로서 코드 중복을 피할 수 있다.
항목 4 : 객체를 사용하기 전에 반드시 그 객체를 초기화하자.
객체를 사용하기 전에 그 객체는 반드시 초기화하는 것이 좋다.[ 정리해 두고 싶은 것 & 추가적인 사항 ]
- 객체 멤버 변수는 선언된 순서대로 초기화 된다. (초기화 리스트에서의 순서는 상관X)
- 생성자 본문에서 초기화를 하면 그건 그냥 이미 초기화된 변수에 대입을 하는 셈. (생성자 본문이 실행되기전에 초기화는 완료된다.) 그래서 반드시 초기화 리스트를 사용해야 하는 것.
POD type이 아닌 지역 정적 객체는 객체 정의에 최초로 닿았을 때 초기화 된다. |
- 위 사진에서 처럼 지역 정적 객체의 생성은 runtime시 생성되었는지 여부가 check된다. 따라서 함수 호출시 마다 assembly instruction 3개 정도의 overload가 추가된다.
#include <iostream>
class B;
class A {
public:
unsigned int checksum;
private:
B& b;
A(B& b)
:b(b), checksum(0x12341234)
{}
public:
static A& A::GetObject();
};
class B {
public:
unsigned int checksum;
private:
A& a;
B(A& a)
:a(a), checksum(a.checksum)
{}
public:
static B& GetObject() {
static B b(A::GetObject());
return b;
}
};
A& A::GetObject() {
static A a(B::GetObject());
return a;
}
int main()
{
std::cout << "A : " << std::hex << A::GetObject().checksum << std::endl;
std::cout << "B : " << std::hex << B::GetObject().checksum << std::endl;
return 0;
}
소스 : https://gist.github.com/taeguk/1c23e5e00c51f7ad36213081cd094438
그리고 저런식으로 컴파일러가 코드를 생성하기 때문에 때문에 circular dependency가 발생하더라도 무한루프에 빠지지 않고 이상현상을 발생시키게 된다. (물론 특정 컴파일러는 무한루프를 발생시킬 수 도 있다. 컴파일러마다 제각기 다를 것이다.)
또, 멀티쓰레드 환경에서는 지역 정적 객체가 여러개 생성되는 문제가 발생할 수 있다.
[ 이것만은 잊지 말자! ]
- 기본제공 타입의 객체는 직접 손으로 초기화합니다. 경우에 따라 저절로 되기도 하고 안되기도 하기 때문입니다.
- 생성자에서는, 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용합시다. 그리고 초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열합시다.
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 합니다. (순서를 예측할 수 없다. 즉 dependency문제) 비지역 정적 객체를 지역 정적 객체로 바꾸면 됩니다.
이것만은 잊지 말자! 는 책에 각 항목 마지막에 있는 것을 거의 그대로 따왔다.
정리해 두고 싶은 것 & 추가적인 사항 는 책의 내용중 특별히 정리해두고 싶은 것과 내가 추가적으로 알아내고 연구해본 것들을 적었다.
근데 어투가 너무 딱딱한듯...ㅠㅜ 다음에는 chapter 2로 만나요~~
댓글
댓글 쓰기