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

Effective C++ 책을 읽고 정리하고 합니다.

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

의미적인 제약 const

const의 가장 아름다운 점은 소스코드 수준에서 사용컴파일러가 해당 제약을 지켜줌 두 가지 일 것이다. 프로그래머가 지정한 제약 수준을 컴파일러가 지켜준다 점 자체가 정말 프로그래밍 언어로 컴퓨터와 대화하는 느낌이니까.. 😌

const는 정말 다방면에서 많이 사용된다. 클래스 바깥에서는 “전역” 혹은 “네임스페이스” 유효범위를 상수로 정의 및 선언을 하는데 사용할 수 있다. 클래수 내부의 경우, “정적멤버” 및 “비정적 멤버” 모두를 상수로 선언 할 수 있다.

포인터가 조금 까다로울 수 있는데, 포인터는 가리키는 대상을 상수화 할 수 있고, 포인터 자체를 상수화 할 수 있고, 둘 다 상수화가 가능하다.

char greeting [] = "Hello";
char halo [] = "halo";

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

const char *p = greeting;		// 비상수 포인터, 상수 데이터
								// 포인터가 가리키는 대상 변화 불가능

char * const p = greeting;		// 상수 포인터, 비상수 데이터
								// 포인터 자체 자체 변화 불가능

const char * const p = greeting;		// 상수 포인터, 상수 데이터
										// 포인터, 가리키는 대상 모두 변화 불가능

추가로 설명하자면, const char *p 로 선언된 케이스에 만약 greeting의 값을 변경하고자 할 경우 아래와 같은 에러를 만나게 된다.

// 본인이 g++로 컴파일 할 때 만난 에러이다.
error: read-only variable is not assignable

즉, 값을 변경할 수 없다는 에러이다. 하지만, halo 라는 값으로 pointer를 변경할 경우 잘 동작한다. 이와 반대로 아래의 char * const pgreeting 값 중 일부를 변경하면 잘 동작하지만, 포인터를 halo 변경할 경우 아래와 같은 에러를 만날 것이다.

// 본인이 g++로 컴파일 할 때 만난 에러이다.
error: cannot assign to variable 'p' with const-qualified type 'char *const'

마지막 pointervariable 둘 다에 const를 붙일 경우 변경은 아무것도 되지않고 컴파일 시 에러를 만날 것이다.

그렇다면 이걸 어떻게 구별할 것인가? (어렵지 않다..🤗)

우리는 * (pointer) 를 기준으로 생각하면 된다. * (pointer) 기준으로 왼쪽에 있을 경우 pointer가 가리키는 대상을 상수화 하는 것이고 오른쪽에 있을 경우 pointer 자체를 상수화 하는 것이다.

항상 * (pointer) 기준으로 생각해야한다. 아래의 예시를 보자.

void f1 (const Widget *pw);		// 포인터가 왼쪽에 있다. 따라서 가리키는 대상이 상수

void f2 (Widget const *pw);		// f1과 동일

일반적으로 문장을 볼 때, Type 기준으로 보는 경향이 있다. Widget 이 아닌 * (pointer) 기준으로 보자. 현업에서 두 가지다 잘 사용하고 있기 때문에 문장을 만났을 때 햇갈리지 말자!

STL 반복자 const

STL 반복자* (pointer)를 본뜬 것이다. 어떤 반복자를 const로 선언하는 것은 포인터 상수화 하는 방법과 동일하다. 반복자 앞에 const를 붙이게 될 경우 가리키는 대상의 상수화 가능하다. 만약 포인터를 상수화 하기 위해서는 const_iterator로 반복자를 선언해 사용하면 된다.

/*
   it 는 const T* 선언과 동일하게 동작한다.
   가리키는 대상의 상수화
*/
const std::vector<int>::iterator it = vec.begin();

*it = 10;		// 포인터 값을 변경하는 것은 가능.
it++;			// 가리키는 대상이 상수화 됐기 때문에 대상의 값 변경 불가능!

/*
  const_iterator 사용
  포인터를 상수화
*/
const std::vector<int>::const_iterator cIt = vec.begin();

*cIt = 10;		// 에러 발생. 포인터 값 변경 불가능
it++;		// 가능하다. 포인터가 가리키는 대상에 대해선 값 변경 가능!

const 함수 선언

const의 가장 강점은 함수 선언 할 때 사용하는 것이다. 함수 선언문에 있어 const 는 “반환값”, “각각의 매개변수” 그리고 “멤버함수” 앞 혹은 전체에 const 성질을 부여할 수도 있다.

🌱 함수의 반환값을 const화 할 경우

함수의 반환 값에 const를 붙여 줄 경우 안정성이나 효율을 포기하지 않고도 사용자 측에서 에러가 발생하는 상황을 줄일 수 있다. 아래의 예시를 보자.

// 유리수 클래스가 있다고 가정하자.
class Rational { ...  };

// 멤버 함수 선언 및 정의
const Rational operator*(const Rational& lhs, const Rational& rhs) {
	...
}

int main() {
	// 사용자 측면에서 에러를 만나게 됨.
	if (a * b = c) ...
}

위와 같이 함수의 반환값을 선언해둘 경우 사용자 입장에서 Rational 이라는 사용할 때 의도치 않는 부분에서 컴파일 시 에러를 먼저 출력해 추후 런타임 버그 가 나는 것을 막을 수 있게해준다.

위의 코드에서 if (a * b = c) 부분을 보자. 사용자는 == 연산을 통해 a * b 값과 c 값을 비교 하고 싶은데, (야근을 해 정신이 혼미한 상태에서.. 🤢) 잘못쳐 = 하나만 쳤다고 가정해보자. 만약 const로 선언하지 않았다면, compiler는 사용자가 operator=를 부르나 보다 하고 그냥 넘어갈 수도 있고, 추후 이상한 버그가 발생해 하루종일 야근을 해야될수도있다..

하지만, const를 작성할 경우 상수에 =을 사용한다는 말도안되는 부분을 컴파일러가 미리 사용자에게 에러로 알려줄 것이다.

😴 포스팅 읽는 시간이 너무 길어지면 집중력이 떨어진다. 잠시 숨 좀 고르고 😮💨 다음 포스팅 을 보도록 하자.