const의 활용

const와 포인터

class EXAMPLE {int ex; }; // 예제 클래스
const EXAMPLE* ep;  // 위와 아래의 코드는 같은 의미이다.
EXAMPLE const* ep;  // *과 const의 위치를 보고 판단하자.
char ex[] = "Hello";

char* p = ex;              // 포인터와 데이터 모두 변수.
const char* p = ex;        // 포인터는 변수, 데이터는 상수. const T *
char* const p = ex;        // 포인터는 상수, 데이터는 변수. T * const
const char* const p = ex;  // 포인터와 데이터 모두 상수.

STL iterator에서의 const

using namespace std;
vector<int> v;
const vector<int>::iterator iter = v.begin();  // T * const와 대응
*iter = 10;  // 데이터는 변경 가능.
++iter;      // 반복자는 다른 대상을 가리킬 수 없다.

vector<int>::const_iterator const_iter = v.begin();  // const T * 과 대응.
*const_iter = 10;  // 데이터 변경 불가능
++const_iter;      // 반복자는 다른 대상을 가리킬 수 있다.

함수에서의 const

  • return Type이 const인 경우.
const EXAMPLE operator*(const EXAMPLE& a, const EXAMPLE& b);
EXAMPLE a, b, c;

if(a+b = c)와 같은 원치 않았던 오류를 막을 수 있다.

상수 멤버 함수

함수 내에서 클래스의 멤버를 변경할 수 없다.

아래 코드를 보면 const의 유무로 오버로딩이 가능하다는 것을 알 수 있다.

class EXAMPLE {
   private:
    string text;

   public:
    EXAMPLE(string s) : text(s){};
    const char& operator[](int position) const {
        cout << "const - const ";
        return text[position];
    }
    char& operator[](int position) {
        cout << "var ";
        return text[position];
    }
};

int main() {
    EXAMPLE e("var");           // 변수
    const EXAMPLE ce("const");  // 상수

    cout << e[0] << '\n';   // 출력 : var v
    cout << ce[0] << '\n';  // 출력 : const - const c
}

비트 수준 상수성과 논리적 상수성

상수 멤버 함수여도 주솟값을 통해 값이 바뀔 수 있다. 이런 경우에 비트 수준 상수성은 갖기 때문에 컴파일러에서 알아챌수가 없다.

상수 멤버 함수에서도 멤버 변수를 수정할 수 있게 mutable을 사용할 수 있다.

class EXAMPLE {
   private:
    mutable bool valid;
    mutable int len;
    string text;

   public:
    int text_length() const {
        valid = true;
        len = text.length(); // valid와 len을 수정할 수 있다.
        return len;
    }
}

상수 멤버 함수와 비상수 멤버함수가 기능적으로는 동일하게 구현되어 있다면 코드 중복을 피하기 위해 const_caststatic_cast를 이용한다.

이때 비상수 멤버함수가 상수 멤버함수를 호출하게끔 한다. 반대의 경우에는 상수 멤버 함수가 의미와는 다르게 비상수 멤버함수를 호출함으로써 클래스 내부를 변경할 수 있기 때문이다.

'C++ > Effective C++' 카테고리의 다른 글

[C++] #define 대신 const, enum, inline 사용하기  (0) 2019.04.03

#define 대신 const, enum, inline 사용하기

키워드

define, const, enum, inline

#define을 쓰면 소스 코드가 컴파일러에게 가기 전에 선행처리자가 숫자 상수로 바꿔버리기 때문에 컴파일러가 쓰는 기호 테이블에 들어가지 않는다.

때문에 숫자 상수로 대체된 코드에서 컴파일 에러가 발생하게 되면 에러 메시지에 숫자 상수가 나와버리기 때문에 자신이 작성하지 않은 코드인 경우 큰 혼란을 겪을 수 있다.

상수 포인터를 정의하는 경우

상수 포인터는 보통 헤더 파일에 넣는 것이 상례이다.

포인터와 포인터가 가리키는 대상 모두 const로 선언하는 것이 좋다.

const char* const Name = "EXAMPLE";
const string Name("EXAMPLE");

클래스 멤버로 상수를 정의하는 경우

class GamePlayer {
   private:
    static const int NumTurns = 5;  // 상수 부분, 선언 부분, 초기값이 있다.
    int scores[NumTurns];           // 상수 사용 부분
};
  • 헤더 파일에 둔다. NumTurns는 선언되어있고 정의되어 있지 않다.
const int GamePlayer::NumTurns; // NumTurns의 정의
  • 정의 부분은 헤더 파일이 아닌 구현 파일에 둔다. 정의에는 초기값이 없다.

#define은 private 같은 기능이 없기 때문에 캡슐화의 기능이 없다. 그렇기 때문에 const를 쓰는 것이 좋다.

오래된 컴파일러에서는 반대의 경우도 있다. 선언 부분에 초기값이 없고 정의 부분에 초기값을 정해준다. 하지만 아래와 같은 예외사항이 있을 수 있다.

class CostEstimate {
   private:
    enum { NumTurns = 5 }; // 나열자를 이용.
    int scores[NumTurns];
};
  • enumconst보단 #define에 가깝다. enum#define의 주소를 얻어내는 것은 불가능하지만 const는 가능하다.

매크로 함수로 사용하는 경우

#define CALL_WITH_MAX(a, b) ((a) > (b) ? (a) : (b))  // 괄호를 유의하여 작성해야한다.
int a = 5, b = 0;
cout << CALL_WITH_MAX(++a, b);       // a가 2번 증가
cout << CALL_WITH_MAX(++a, b + 10);  // a가 1번 증가
template <typename T>
inline void callWithMax(const T& a, const T& b) {
    cout << (a > b ? a : b);
}

inline을 이용해서 함수를 쓰면 진짜 함수이기 떄문에 유효 범위 및 접근 규칙을 적용할 수 있다. 괄호 때문에 생기는 혼란도 없앨수 있다.

'C++ > Effective C++' 카테고리의 다른 글

[C++] const의 활용  (1) 2019.04.16

+ Recent posts