태크놀로지

[Effective C++] 왜 const를 사용해야하는가 본문

C++

[Effective C++] 왜 const를 사용해야하는가

원택 2020. 11. 18. 02:29

1. define 대신 const, enum, inline을 사용하자

왜?
#define ASPECT_RATIO 1.653 의 경우
우리는 ASPECT_RATIO로 알지만, 컴파일러에게 넘어가기전 숫자 상수로 밀어버리게 됨
에러메시지에 ASPECT_RATIO가 아닌 1.653으로 나타남

 

따라서
매크로 대신 const 상수를 사용하자!
상수를 사용할 경우 기호 테이블에 들어감. 디버깅시 확인가능
부동소수점 실수 타입일 경우, 컴파일을 거친 최종 코드 크기가 더 작게 나올 수 있음
-> 코드에서 ASPECT_RATIO를 사용한 코드마다 1.653으로 변경하는 사본이 생기기 때문

 

2. 클래스 내에 static 멤버

// header
class GamePlayer{
private:
	static const int NumTurns;	// 상수 선언 (정의 X 가능)
}
// cpp
//const int GamePlayer::NumTurns = 5;
int main()
{
	// cout << GamePlayer::NumTurns << endl; 
  	// 주석 O : 오류없음 static 메모리 접근이 없기때문에 메모리할당이 없음
    	// 주석 X : compile error : static 초기화 필요
}

주소를 취하지 않는 한, 정의 없이 선언만 해도 아무 문제가 없게 되어있음. 
이 때 클래스 상수의 정의는 cpp파일에 두어야한다. h에 놓을경우 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 초기화됨

 

(1~2) 정리

- 단순한 상수를 사용할때는, #define보다 const객체 혹은 enum을 우선 생각하자.
- #define 매크로 함수는 좋지않다. 인라인(template) 함수를 우선으로 만들자


3. const 위치에 따른 정의

const char* p : 객체가 const / 포인터는 non-const

(char const* p와 동일)

 

char* const p : 포인터가 const / 객체는 non-const
(즉 객체를 가리키고 있는 포인터를 변경할 수 없다.)

 

const char* const p : 둘다 const

[예제]

int main() {
	vector<int> vec;

	const vector<int>::iterator iter = vec.begin();

	// 반복자가 const 따라서 반복자를 변경하지 못함
	*iter = 10; 
	// ++iter; 에러

	vector<int>::const_iterator cIter = vec.begin();
	
	// 객체의 상수성을 갖고있음 따라서 반복자 변경가능하지만, 객체 변경 불가능
	// *cIter = 10; 에러
	++cIter;
}

 

4. 상수 멤버 함수

예시)
const X operator*()일경우
if (a*b = c) 컴파일러에서 이러한 오타방지 가능 (==인데 =만 쓴경우)

const X operator[] () 일경우 : X의 값이 const
X operator[] () const 일경우 : 함수 자체가 const (operator 함수내에 값변경이 일어나지 않음.)

 

5. 논리적 상수성

const 객체로 생성했지만, 값이 변경되어 버리는 상황이 발생

class CTextBlock {
public:
	CTextBlock(char* str) {
		pText = str;
	}
	~CTextBlock() = default;

	// &가 없을시 컴파일오류 : 원본이 아님

	// const
	/* const */ char& operator[](size_t position) const {
		return pText[position];
	}

	// non-const
	char& operator[](size_t position) {
		return pText[position];
	}

private:
	char* pText;
};

int main()
{
	char a[6] = "Hello";

	const CTextBlock ctb(a);
	ctb[0] = 'x';

	// 논리적 상수성
	// 비트수준 상수성에서 알지못함.
	// 비트수준 상수성: 구성하는 비트중 어떤것도 바꾸면 안됨
	// 객체가 const지만 값이 바뀌어버림
	cout << ctb[0] << endl;
}

-> 상수형과 비상수형 operator함수를 제작하자.

 

6. const멤버함수 및 non-const 멤버함수내에서 중복현상 피하기

mutable을 사용시 const함수내에서 쓰기가 가능
상수멤버함수와 비상수멤버함수에서 같은 검사 코드가 발생했을시, 중복코드를 없애보자.

class CTextBlock {
public:
	CTextBlock(char* str) {
		pText = str;
	}
	~CTextBlock() = default;

	// &가 없을시 컴파일오류 : 원본이 아님

	// const
	const char& operator[](size_t position) const {

		// 경계검사
		// 접근 데이터 로깅
		// 자료 무결성 검증
		if (!lengthIsValid) {
			textlength = strlen(pText);
			lengthIsValid = true;
		}

		return pText[position];
	}

	// non-const : 중복코드 제거
	char& operator[](size_t position) {
		
		return
			// const 떼어내기
			const_cast<char&>(

				// const로 형변환 후
				// const 연산자 실행
				static_cast<const CTextBlock&>
				(*this)[position]
				);
	}

private:
	char* pText;

	mutable size_t textlength;
	mutable bool lengthIsValid;
};

-> 비상수멤버함수에서 상수멤버함수를 호출

 

(3~6) 정리

- const를 붙여 선언하면 컴파일러가 에러를 잡는데 도움을 줌. 안전성
논리적 상수성을 사용해서 프로그래밍 해야함
상수멤버 및 비상수 멤버함수가 기능적으로 서로 똑같을 경우, 코드중복을 피하기 위해 비상수함수가 상수함수를 호출하도록 만들어라.

 


출처

effective c++ / 스콧 마이어스