C++를 공부한 당신이 꼭 알아야 할 10가지 핵심 세부사항

기본적인 타입 초기화의 중요성

기본 타입은 컴파일러에 의해 자동으로 초기화되지 않습니다. 예를 들어, int x;와 같이 선언했을 때, x의 값은 보장되지 않으며 사용 전 반드시 명시적으로 초기화해야 합니다.

int x = 0;                    // 명시적 초기화
const char* text = "hello";   // 포인터도 초기화 필요

생성자 내에서 멤버 변수 초기화하기

클래스의 멤버 변수는 생성자 본체 내에서 할당하는 것이 아니라, 멤버 초기화 리스트를 사용해 초기화해야 효율적입니다. 이는 불필요한 기본 생성자 호출과 복사 과정을 피할 수 있습니다.

class Student {
public:
    Student(int id, const std::string& name, const std::vector<int>& scores)
        : m_id(id),           // 초기화 리스트
          m_name(name),
          m_scores(scores)
    { /* 생성자 본체는 비어 있어도 됨 */ }
private:
    int m_id;
    std::string m_name;
    std::vector<int> m_scores;
};

non-local static 객체의 초기화 순서 문제 해결

다른 컴파일 단위에 존재하는 static 객체 간 초기화 순서는 보장되지 않습니다. 이를 해결하기 위해 각 static 객체를 함수 내부에 static로 선언하고, 그 함수를 통해 접근하는 방식을 사용합니다.

class FileSystem {
public:
    static FileSystem& get_instance() {
        static FileSystem instance;  // 지역 정적 객체
        return instance;
    }
    // ...
};

// 사용 시
std::size_t disks = FileSystem::get_instance().num_disks();

const 키워드의 활용

const는 변수, 포인터, 반복자, 함수 등 어디에나 적용 가능하며, 컴파일 타임 오류 탐지에 도움을 줍니다. 특히 멤버 함수에 const를 붙이면 const 객체에서도 호출할 수 있고, 성능 최적화에도 기여합니다.

class MyString {
public:
    const char& operator[](std::size_t pos) const {
        // 경계 검사, 로깅 등
        return data[pos];
    }

    char& operator[](std::size_t pos) {
        return const_cast(
            static_cast<const MyString&>(*this)[pos]
        );
    }
private:
    std::string data;
};

operator=의 반환 형식: 참조 반환

연쇄 할당을 가능하게 하려면 operator=*this에 대한 참조를 반환해야 합니다. 이는 a = b = c;와 같은 문법을 지원합니다.

class A {
public:
    A& operator=(const A& rhs) {
        // ... 복사 로직
        return *this;  // 참조 반환
    }
};

자기 할당 처리 (Self-assignment)

객체가 자신에게 할당될 경우, 포인터가 이미 삭제된 메모리를 가리킬 수 있습니다. 이를 방지하려면 다음 중 하나의 방법을 사용하세요:

  • 주소 비교: if (this == &rhs) return *this;
  • 순서 조정: 먼저 새 메모리 할당 후 원래 메모리 삭제
  • copy-and-swap 기법: 임시 객체를 만들어 스왑

복사 함수에서 모든 요소 복제

사용자가 직접 복사 생성자 또는 대입 연산자(예: operator=)를 구현하면 컴파일러는 기본 버전을 생성하지 않습니다. 이때 모든 멤버 변수와 상속받은 부모 클래스의 상태도 올바르게 복제해야 합니다.

CollegeStudent::CollegeStudent(const CollegeStudent& rhs)
    : Student(rhs),      // 부모 클래스 복사
      major(rhs.major)
{ }

CollegeStudent& CollegeStudent::operator=(const CollegeStudent& rhs) {
    Student::operator=(rhs);  // 부모 클래스 할당
    major = rhs.major;
    return *this;
}

자동 생성되는 함수 이해

비어 있는 클래스를 정의하면 컴파일러가 다음 함수들을 암묵적으로 생성합니다:

  • 기본 생성자
  • 복사 생성자
  • 대입 연산자
  • 소멸자

하지만 멤버 변수에 const 또는 참조가 포함되어 있으면 operator=는 생성되지 않습니다.

다형성 기반 클래스의 가상 소멸자

다형성 클래스의 소멸자에는 항상 virtual을 붙여야 합니다. 그렇지 않으면 파생 클래스의 소멸자가 호출되지 않아 메모리 누수가 발생할 수 있습니다.

class Base {
public:
    virtual ~Base() {}  // 반드시 가상
};

class Derived : public Base {
public:
    ~Derived() { /* 파생 클래스 소멸자 */ }
};

생성자/소멸자 내에서 가상 함수 호출 금지

생성자나 소멸자 내에서는 virtual 함수가 실제로 파생 클래스의 버전을 호출하지 않습니다. 이는 객체가 아직 완전히 구성되지 않았거나 이미 분해되기 시작했기 때문입니다.

#define보다는 const, enum, inline 사용

#define는 전처리 단계에서 단순 치환만 수행되며, 디버깅이 어렵고 이름 공간을 오염시킵니다. 대신 const, enum, inline을 사용하세요.

const double PI = 3.14159;                // const로 상수 정의
enum { MAX_SIZE = 100 };                  // class 내부 상수
template<typename T>
inline T max(const T& a, const T& b) {    // inline 함수로 함수 매크로 대체
    return a > b ? a : b;
}

태그: const inline member initialization copy constructor operator=

6월 21일 00:25에 게시됨