이것만은 잊지 말자!

- 기본제공 타입의 객체는 직접 초기화, 경우에 따라 자동으로 되기도 안되기도 함

- 생성자에서는 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화하지 말고 멤버 초기화 리스트를 즐겨 사용하자
  초기화 리스트에 데이터 멤버를 나열할 때는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열하자
  
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 함
  싱글톤 패턴을 사용하여 비지역 정적 객체를 지역 정적 객체로 바꾸면 됨

객체의 일부는 값이 자동으로 초기화되는 것이 보장되지 않을 수 있으므로 미정의 동작을 방지하기 위해 항상 초기화하자

C++의 객체 초기화의 규칙이 정해져있지만 기억하기에는 너무 복잡함

 

대입과 초기화를 헷갈리지 말자

C++ 규칙에서 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화돼야 함

대입할 경우 데이터 멤버의 기본 생성자가 호출된 뒤 할당 연산자가 호출되는 불필요한 연산 발생

멤버 초기화 리스트를 사용하여 복사 생성자를 호출하는 것이 더 효율적

class ABEntry
{
public:
    // 대입
    ABEntry(const int age)
    {
        _age = age;
    }
    
    // 초기화
    ABEntry(const int age)
        : _age(age)
    {}

private:
    int _age;
};

 

멤버 초기화

모든 멤버를 초기화하는 습관을 들여 초기화를 빼먹는 실수를 방지

상수이거나 참조자로 되어 있는 데이터 멤버는 대입이 불가능하므로 반드시 초기화돼야 함

 

각 생성자마다 데이터 멤버를 초기화하는 것이 중복된다면 private 멤버 함수에서 초기화하는 것도 고려

 

기본 클래스는 파생 클래스보다 먼저 초기화되고, (생성자 멤버 초기화 리스트에 다른 순서를 넣더라도) 데이터 멤버가 선언된 순서대로 초기화

따라서 버그를 방지하기 위해 멤버 초기화 리스트에 넣는 순서도 선언 순서와 동일하게 하자

 

비지역 정적 객체 초기화 순서는 개별 번역 단위에서 정해짐

정적 객체

전역 객체

네임스페이스 유효범위에서 정의된 객체

클래스 안에서 static으로 선언된 객체

함수 안에서 static으로 선언된 객체

파일 유효범위에서 static으로 정의된 객체

 

번역 단위

컴파일을 통해 하나의 object file을 만는 소스 코드

기본적으로 소스 파일 하나에 #include 하는 파일들까지 합쳐서 하나의 번역 단위

 

별개의 번역 단위에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져 있지 않음

따라서 다른 번역 단위의 정적 객체를 사용할 경우 초기화되어 있지 않을 수 있음

 

함수에서 정적 객체를 선언하고 참조자를 반환하도록 구현(싱글톤 패턴)

멀티스레드 시스템에서는 초기화와 관련하여 race condition으로 동작에 장애가 생길 수 있으므로 멀티스레드 돌입 전 시동 단계에서 함수를 호출하여 초기화를 미리 하는 방법도 있음

class FileSystem { ... };

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

이것만은 잊지 말자!

- 단순한 상수를 쓸 때는 #define보다 const 혹은 enum을 우선 생각
- 함수처럼 쓰이는 매크로를 만들려면 #define 매크로보다 인라인 함수를 우선 생각

#define

상수를 #define으로 표현했을 때 단점

컴파일러가 매크로를 숫자 상수로 변경하므로 에러 메시지의 숫자 상수가 어디서 나왔는지 알 수 없음

 

#define을 상수로 교체할 때 주의할 점

상수 포인터를 정의할 때 상수 정의는 대개 헤더 파일에 넣어 다른 파일에서 include해서 사용하므로 포인터는 꼭 const로 선언하고, 포인터가 가리키는 대상까지 const로 선언하는 것이 일반적

문자열 사용할 때 char* 보다는 string 사용하는 것이 좋음

메모리를 직접 관리하지 않아도 돼 메모리 누수를 피할 수 있고, 다양한 멤버 함수 등으로 가독성, 편의성 등 더 유리

const char* const authorName = "Scott Meyers"; 
const std::string authorName("Scott Meyers");

 

클래스 멤버로 상수를 정의할 때 상수의 사본 개수가 1개를 넘지 못하게 하고 싶으면 정적(static) 멤버로 만들어야 함

정적 멤버로 만들어지는 정수류(정수 타입, char, bool 등) 타입의 클래스 내부 상수는 예외로 주소를 취하지 않는 한 정의 없이 선언만 해도 구현 가능

단, 클래스 상수의 주소를 사용하거나 컴파일러가 정의를 요청할 때 제공해야 함

class GamePlayer
{
private:
    static const int NumTurns = 5;
    int scores[NumTurns];
};

 

클래스 상수의 정의는 구현 파일에 두어야 함

클래스 상수의 초기값은 선언된 시점에서 초기화되므로 정의에서 초기값이 있으면 안됨

const int GamePlayer::NumTunrs;

 

클래스 상수를 #define으로 만들지 말자

#define은 컴파일이 끝날 때까지 혹은 #undef될 때까지 유효하므로 클래스에 정의하는데 쓸 수 없음, 어떤 형태의 캡슐화도 불가능

 

나열자 둔갑술(Enum Hack)

enum을 사용하여 #define 방식에 가깝게 상수 사용 가능

enum / enum class는 메모리를 할당하지 않아 주소를 취하는 것이 불가능

컴파일 타임 상수

class GamePlayer{
private:
    enum { NumTurns = 5 };
        
    int scores[NumTurns];
}

 

enum class가 enum과의 차이점

암시적 변환 X

서로 다른 enum class 간 혼합 사용 불가하여 안정성 증가

enum은 전역 네임스페이스에서 사용 가능 / enum class는 해당 네임 스페이스 내 정의

 

매크로 함수

사용 이유 : 함수 호출 오버헤드 발생 X

매크로 본문에 있는 인자마다 괄호를 사용하는 것이 좋음

#define sqaure(x) x * x

int a = sqaure(5 + 1); // 기대하는 결과는 (5 + 1) * (5 + 1) = 36이지만 5 + 1 * 5 + 1 = 11
int b = square(++a); // a가 두 번 증가 : ++a * ++a로 치환되기 때문

 

인라인 함수를 통해 매크로 효율을 유지하고 정규 함수의 동작 방식 및 타입 안정성까지 챙길 수 있음

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

이것만은 잊지 말자!

- C++를 사용한 효과적인 프로그래밍 규칙은 C++의 어떤 부분을 사용하느냐에 따라 달라짐

용어

선언

코드에 사용되는 어떤 대상의 이름과 타입을 컴파일러에게 알려주는 것

구체적인 세부사항은 들어 있지 않음

extern int x;
class Widget;

 

정의

선언에서 빠진 구체적인 세부사항을 컴파일러에게 제공

객체의 정의는 컴파일러가 객체의 메모리를 마련하는 것

함수나 함수 템플릿에 대한 정의는 코드 본문을 제공하는 것

클래스나 클래스 템플릿에 대한 정의는 클래스 혹은 템플릿의 멤버를 넣어 준 결과

int x; // 객체 정의

std::size_t numDigits(int number) // 함수 정의
{
    return 1;
}

class Widget
{
    // 클래스 정의
public:
    Widget();
};

 

초기화

객체에 최초의 값을 부여하는 과정

 

기본 생성자

매개변수가 없거나 모든 매개변수가 기본 값을 가지고 있는 생성자

 

explicit

암시적 타입 변환에 생성자가 사용되지 않게 막는 키워드
암시적 타입 변환을 사용할 여지가 없다면 우선적으로 explicit로 선언

 

복사 생성자

객체의 초기화를 위해 같은 타입의 객체로부터 초기화할 때 호출되는 함수

 

복사 대입 연산자

같은 타입의 다른 객체에 어떤 객체의 값을 복사하는 용도

class Widget
{
public:
   Widget& operator= (const Widget& rhs); // 복사 대입 연산자
}

Widget w1;
Widget w3 = w2; // 객체가 새로 정의되는 것으로 복사 생성자가 호출됨
w1 = w2; // 복사 대입 연산자 호출

 

선행 처리자

#define

 

size_t

unsigned int를 typedef로 선언한 것

운영체제 메모리 크기에 따라 달라짐, 32비트 운영체제면 32bit, 64비트 운영체제면 64bit
메모리나 문자열 등의 길이를 구할 때 사용

 

시그니처

함수의 타입을 의미

함수의 매개변수 리스트와 반환 타입이 포함됨

 

c++은 다중 패러다임 프로그래밍 언어

절차적 프로그래밍을 기본으로 객체 지향, 함수식, 일반화, 메타 프로그래밍 개념까지 지원
일반화 : 템플릿을 이용한 프로그래밍(Common하게 사용할 수 있는 프로그래밍을 의미하는 듯)

+ Recent posts

목차