C++/Effective C++

Effective C++ 공부 (항목 6 ~ 10)

둠치킨 2025. 6. 24. 19:26

Effective C++ 요약 (항목 6~10)


항목 6: 컴파일러가 만들어주는 함수를 원치 않으면 명시적으로 금지하라

컴파일러는 특별한 요청이 없어도 복사 생성자와 대입 연산자를 자동으로 생성한다. 하지만 어떤 객체는 복사되면 안 되는 경우가 있다.

이럴 땐 다음처럼 private으로 선언하고 구현하지 않기:

class A {
private:
    A(const A&);
    A& operator=(const A&);
};

이러면 외부 복사가 불가능해진다. 만약 friend 함수가 사용하면 컴파일은 되지만 링커 오류 발생.

현대적 방법 (C++11 이후):

class A {
public:
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};
  • 가독성, 유지보수성 증가
  • 의도가 명확하게 드러남

항목 7: 다형성을 가진 기본 클래스에는 가상 소멸자를 반드시 넣어라

다형성(polymorphism)을 지원하는 기본 클래스는 소멸자를 virtual로 선언해야 한다.
그렇지 않으면 파생 클래스 포인터를 기반 클래스 포인터로 삭제할 때 정의되지 않은 동작 발생.

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

class Derived : public Base {
    ~Derived();
};

추가: 가상 함수가 없어도 추상 인터페이스 클래스를 만들고 싶으면 소멸자를 순수 가상으로:

class Abstract {
public:
    virtual ~Abstract() = 0;
};
Abstract::~Abstract() {} // 정의 필요

주의: STL 컨테이너나 string을 상속하려고 하지 말자. 그들은 가상 소멸자를 갖지 않음.


항목 8: 소멸자에서 예외를 던지지 말자

소멸자에서 예외가 발생하면 다른 예외와 겹쳐져 프로그램이 비정상 종료될 수 있다.

class FileHandle {
public:
    ~FileHandle() {
        try {
            close(); // 예외 발생 가능성 있음
        } catch (...) {
            // 삼켜서 예외 전파 방지
        }
    }
};

더 좋은 방법: 사용자가 명시적으로 close()를 호출하게 만들고, 실패하면 직접 처리하도록 하자.

class FileHandle {
public:
    void close();
    ~FileHandle() {
        try { close(); } catch (...) {}
    }
};

항목 9: 생성자와 소멸자에서 가상 함수를 호출하지 말자

객체의 생성자 또는 소멸자 안에서는 가상 함수가 재정의되지 않은 상태로 호출된다. 아래 예시는 위험하다:

class Transaction {
public:
    Transaction() { LogTransaction(); }  // X 가상 함수 호출
    virtual void LogTransaction() const = 0;
};

해결법: 가상 함수 대신 비가상 함수 호출 + 필요한 데이터를 생성자 매개변수로 전달

class Transaction {
public:
    explicit Transaction(const std::string& logInfo) {
        LogTransaction(logInfo);
    }
    void LogTransaction(const std::string& logInfo);
};

class BuyTransaction : public Transaction {
public:
    BuyTransaction() : Transaction(CreateLogString()) {}
private:
    static std::string CreateLogString();
};

항목 10: 대입 연산자는 *this의 참조를 반환하라

대입 연산자를 연속해서 사용하는 패턴을 허용하려면 *this를 반환해야 한다.

class Widget {
public:
    Widget& operator=(const Widget& rhs) {
        // 대입 처리
        return *this; // this의 참조 반환
    }
};

이것은 다음과 같은 문법을 가능하게 해 준다:

x = y = z = 15;

STL 및 기본형 타입들도 이 규약을 따르므로, 사용자 정의 타입도 따라야 한다.