[C++] 항목 3: 낌새만 보이면 const를 들이대 보자!

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 정리하였습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 1. C++에 왔으면 C++의 법을 따릅시다Permalink

👉🏻 항목 3: 낌새만 보이면 const를 들이대 보자!Permalink

1. const 키워드Permalink

char greeting[] = "Hello";

// 1. 데이터 수정 O, 포인터 수정 O
char * p = greeting;

// 2. 포인터 수정 O, 데이터 수정 X
const char * p = greeting;

// 3. 포인터 수정 X, 데이터 수정 O
char * const p = greeting;

// 4. 포인터 수정 X, 데이터 수정 X
const char * const p = greeting;

const*의 앞에 있는가, 뒤에 있는가? 에 따라 의미가 달라진다.

// 아래 두 함수는 똑같다.
// 상수 Widget 객체를 가진다.
void f1(const Widget * pw);
void f2(Widget const * pw);

또한 const타입의 위치는 상관이 없다.

즉, 위의 f1과 f2는 같은 함수이다.

using namespace std;

vector<int> vec;
// T * const처럼 동작한다.
const vector<int>::iterator iter = vec.begin();
*iter = 10; // O, 데이터 수정 가능
++iter; // X, 주소 수정 불가능

// const * T처럼 동작한다.
vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; // X, 데이터 수정 불가능
++cIter; // O, 주소 수정 가능

반복자에서 const를 사용하고 싶다면, 위의 방법을 사용해야 한다.

class Rational { ... };
// const 반환값, const 매개변수
const Rational Operator*(const Rational& lhs, const Rational& rhs);

Rational a,b,c;
(a*b) = c; // 에러! 
if(a*b = c); // 에러!

const 키워드는 함수에서도 빛을 발한다.

const가 포함된 결과 값대입을 하는 어처구니 없는 실수를 예방할 수 있다.

1.1. 상수 멤버 함수Permalink

class TextBlock {
public:
	const char& operator[](size_t position) const {
		return text[position];
	}
	char& operator[](size_t position) {
		return text[position];
	}
	
private:
	string text;
}

const char& operator const { … }

위의 멤버 함수에서 const가 끝에 붙은 것을 확인할 수 있다.

이것은 “해당 멤버 함수가 상수 객체에 대해 호출된 함수”라는 것을 알려준다.

아래와 같은 코드에 해당한다.

const TextBlock ctb(”World”); → 이것은 상수 객체로 선언되었다.
cout « ctb[0];

이를 통해 얻을 수 있는 장점은 두가지가 있다.

1. 클래스의 인터페이스를 이해하기 좋아진다.  
→ 클래스로 만들어진 ‘객체를 변경할 수 있는 함수’와 ‘변경할 수 없는 함수’의 구분을  
사용자 측에서 하기가 쉬워진다.
2. 상수 객체를 사용할 수 있게 된다.  
→ 코드의 성능을 높이기 위해선 객체 전달을  
‘상수 객체에 대한 참조자(reference-to-const)’로 진행해야 한다.

위와 같은 장점을 얻을 수 있기에 알아두고 사용하는 것이 좋다.

1.2. const 멤버 함수 오버로딩Permalink

class TextBlock {
public:
	// 1번
	const char& operator[](size_t position) const {
		return text[position];
	}
	// 2번
	char& operator[](size_t position) {
		return text[position];
	}
	
private:
	string text;
}

위에서 언급했던 이 코드에서 두 멤버 함수는 const의 차이만 있다.

이러한 멤버 함수들은 오버로딩이 가능하다.

TextBlock tb("Hello");
cout << tb[0]; // 1번 멤버 함수 호출

const TextBlock ctb("World");
cout << ctb[0]; // 2번 멤버 함수 호출

객체를 생성한 뒤, 위와 같이 사용하면 각자 다른 멤버 함수를 호출할 수 있다.

상수 객체가 생기는 경우는 다음과 같다.

  1. 상수 객체에 대한 포인터로 객체가 전달
  2. 상수 객체에 대한 참조자로 객체가 전달

    cout << tb[0]; // O
    tb[0] = 'x'; // O
    cout << ctb[0]; // O
    ctb[0] = 'x'; // X, ctb 객체에 쓰기는 불가능하다.
    

여기서 눈여겨 보아야 할 점들이 있다.

  1. 두번째 줄에서 tb[0] = ‘x’는 tb.text[0]의 사본에 값을 대입하는 것이므로 실질적으로 수정되지 않는다.
  2. 네번째 줄에서 발생한 에러는 operator[]의 반환 타입이 const이기 때문에 생긴 에러이다.

1.3 비트수준(물리적) 상수성과 논리적 상수성Permalink

비트수준 상수성: 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 ‘const’임을 인정한다.

논리적 상수성: 일부 몇 비트 정도는 수정할 수 있되, 그것을 사용자 측에서 알아채지 못하면 된다.

비트수준 상속성은 문제가 있다.

멤버함수 내에서는 변수가 수정되지 않지만, 외부에서 수정되는 경우가 생길 수 있다.

class CTextBlock {
public:
	char& operator[](size_t position) const {
		return pText[position];
	}

private:
	char* pText;
};

const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';

위의 코드가 그 예시로 함수의 결과 값이 주소이기에, 주소가 가리키는 값을 수정하고 있다.

이런 상황을 보완하기 위해 나온 개념이 논리적 상수성이다.

1.4. mutable 키워드Permalink

class CTextBlock {
public:
	size_t length() const;
	
private:
	char* pText;
	size_t textLength;
	bool lengthIsValid;
};

size_t CTextBlock::length() const {
	if(!lengthIsValid) {
		textLength = strlen(pText); // 에러!
		lengthIsValid = true; // 에러!
	}
	
	return textLength;
}

위의 코드는 비트수준 상수성에도 맞지 않으며, const가 포함된 멤버 함수 내에서

변수를 수정하려하고 있으므로, 에러가 난다.

이런 상황을 위해 나온 것이 mutable 키워드이다.

...
mutable size_t textLength;
mutable bool lengthIsValid;
...

변수를 선언할 때, 앞에 mutable만 붙여주면 const의 제한에서 예외 대우를 받게 된다.

1.5. 코드 중복 제거Permalink

class TextBlock {
public:
	const char& operator[](size_t position) const {
		...
		return text[position];
	}
	
	char& operator[](size_t position) {
		return const_cast<char&>( // const를 떼어냄
			static_cast<const TextBlock&> // const를 붙임
			(*this)[position] // operator[]의 상수 버전 호출
		);
	}
};

const가 붙고 안붙고 차이만 있는 함수를 만들려고 한다.

이를 만들기 위해 중복된 코드를 두번 사용하지 않는 방법도 있다.

  1. const 키워드를 붙인다.
  2. operator의 상수 멤버함수를 호출한다.
  3. const 키워드를 뗀다.

cpp 카테고리 내 다른 글 보러가기

댓글남기기