ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] C++에 왔으면 C++의 법을 따릅시다
    프로그래밍 기초/C++ 2022. 6. 1. 10:31

      C++에 왔으면 C++의 법을 따릅시다 

    #define을 쓰려거든 const, enum, inline을 떠올리자

    클래스 범위에 상수 정의

    클래스 범위에만 상수를 적용할 때는 정적(static) 멤버로 만드는 게 좋다.

    이때, 헤더(.h 파일)에는 선언만 존재해야 하고 정의는 구현(.cpp 파일)에 들어가야 한다.

     

    ※ 클래스 컴파일 시 상수 값이 필요한 경우는 예외로 둔다.


    // CostEstimate.h

    class CostEstimate {

        static const double FudgeFactor;

    }

    // CostEstimate.cpp

    const double CostEstimate::FudgeFactor = 1.35;



    나열자 둔갑술(enum hack)

    클래스 컴파일 시 상수 값이 필요한 경우 상수 대신 enum을 선언하여 사용할 수 있다.

     

    ※ enum은 쓸데없는 쓸데없는 메모리 할당이 발생하지 않는다.


    class GamePlayer {

    private:

        enum { NumTurns = 5 };

     

        int scores[NumTurns];

    }


     

    낌새만 보이면 const를 들이대 보자!

    포인터의 상수 처리

    포인터는 const를 어디에 붙이는지에 따라 포인터를 상수 처리할지, 데이터를 상수 처리할지 달라진다.


    char greeting[] = "Hello";

    char* p = greeting; // 비상수 포인터, 비상수 데이터

    const char* p = greeting; // 비상수 포인터, 상수 데이터

    char* const p = greeting; // 상수 포인터, 비상수 데이터

    const char* const p = greeting; // 상수 포인터, 상수 데이터


     

    ※ 값의 수정이 필요한 경우가 아니면 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;

    }



    논리적 상수성

    함수 내부적으로 상수성이 지켜지지 않더라도 사용자 측에서 알아채지 못하면 상수로 인정한다는 개념이다.


    class CTextBlock {

    public:

        std::size_t length() const;

    private:

        char* pText;

        mutable std::size_t textLength; // mutable 선언으로 상수 처리를 회피한다.

        mutable bool lengthIsValid;

    }

     

    std::size_t CTextBlock::length() const

    {

        if (!lengthIsValid) {

            textLength = std::strlen(pText); // 상수 함수 내부에서 의미 있는 처리가 진행될 수 있다.

            lengthIsValid = true;

        }

        return textLength;

    }


    다만 mutable을 이용한 상수 함수를 생성하면 비상수 함수와 동일한 기능을 수행하는 중복이 발생할 수 있다.

    이때, 비상수 함수에서 상수 함수를 호출하는 식으로 처리하면 코드 중복을 막을 수 있다.

     


    char& operator[](std::size_t position)

    {

        return const_cast<char&> (

            static_cast<const TextBlock&>(*this)[position]; // 상수 버전 호출

        );

    }


     

    ※ 상수 함수에서 비상수 함수를 호출하는 것은 값을 바꿀 수 있는 위험이 있으므로 하면 안 된다.

     

    객체를 사용하기 전에 반드시 그 객체를 초기화하자

    객체 사용 전 초기화 처리

    객체 사용 전에는 항상 초기화 처리를 해 줘야 한다. C++에서 사용하는 C 부분은 객체를 초기화해준다는 보장이 없다.

    어떤 게 초기화를 보장하고 어떤 게 그렇지 않은지 모두 외울 수 없으므로 항상 초기화를 해 줘야 한다.

     

    멤버 초기화 리스트 사용

    생성자 내에서 대입을 하는 대신 멤버 초기화 리스트로 초기화를 해주는 게 좋다.


    ABEntry::ABEntry(const std::string& name, const std::string& address)

    {

        theName = name;

        theAddress = address; // 이건 초기화가 아니라 대입이다.

    }

     

    ABEntry::ABEntry(const std::string& name, const std::string& address)

        : theName(name), theAddress(address)

    { }

     

    ABEntry::ABEntry()    : theName(), theAddress() // 각각 기본 생성자가  호출되도록 한다.{  }


     

    클래스 초기화 순서

    클래스의 초기화 순서는 어느 컴파일러를 사용하더라도 동일하다.

    1. 기본 클래스가 파생 클래스보다 먼저 초기화된다.
    2. 클래스의 멤버 변수는 선언된 순서대로 초기화된다.

    멤버 변수도 초기화 순서에 따라 에러가 발생하는 경우도 있으니 초기화 위치를 순서에 맞게 배치해야 한다.

     

    정적 객체의 초기화 방법

    함수 안에서 선언된 지역 정적 객체를 제외한 정적 객체(전역, 네임스페이스, 클래스, 파일)는 초기화 순서가 정해져 있지 않다. 그로 인해 초기화되지 않은 객체를 사용하려고 하는 문제가 발생할 수 있다.

     

    이를 막기 위해서 정적 객체의 레퍼런스를 반환하는 함수를 만들어 사용하면 된다.


    FileSystem& tfs()

    {

        // 함수 호출 시 해당 객체의 선언이 최초로 실행되는 때에 초기화되므로 초기화 순서를 고정시킬 수 있다.

        static FileSystem fs;

        return fs;

    }

     

    Directory::Directory(params)

    {

        std::size_t disks = tfs().numDisks();

    }

     


     

    ※ 함수 호출이 잦아질 수 있으나 멀티 스레드 등에서 정적 객체를 사용하다 문제가 발생하는 것을 방지할 수 있다는 장점이 있다.

     

    댓글

Designed by Tistory.