같은편 2022. 8. 25. 14:40

const를 이용해 쉽게 상수를 만들어 사용할 수 있습니다.

근데 솔직히 const를 신경 쓰지 않고 코드를 작성해도 프로그램에 오류나 문제는 전혀 없습니다.

그렇다면 왜 쓰는 것일까요? (´・ω・`)?

이유는 개발자의 실수를 줄이기 위해서입니다.

개발자가 무심코 상수를 수정하려고 하면 컴파일러가 에러 메시지를 출력합니다.

 

저도 책의 예제인 MonsterWorld라는 게임을 이해하고 따라 적으면서 매개변수를 const로 전해줘야 되는걸 예전 버전에서는 안 해도 되다가 현재 버전에서는 필수로 const로 전달해줘야 하는 것 때문에 실수를 방지한 부분이 있습니다. 

예를 들어

void print(const char *title = "<My MonsterWorld>") {...}

같은 char 포인터나

연산자 중복할 때

bool operator== (const Point &p) { return x == p.x && y == p.y; }
bool operator!= (const Point &p) { return x!= p.x || y!= p.y; }
Point operator- (const Point &p) { return Point(x - p.x, y - p.y); }
Point operator+ (const Point &p) { return Point(x + p.x, y + p.y); }
void operator+= (const Point &p) { x += p.x, y += p.y; }
void operator-= (const Point &p) { x -= p.x, y -= p.y; }

이런 코드들이 책의 예제에서는 const가 없는데 현재는 const를 안 적으면 컴파일러에서 에러를 띄우고 프로그램이

안되더군요..

안 되는 이유를 몰라 조금 힘들었습니다.

그만큼 const는 개발자의 실수를 방지하기 위해 쓰입니다.

그러므로 값이 변경될 필요가 없는 것 들엔 const를 붙이는 습관을 들이는 것도 좋습니다.ο(=•ω<=)ρ⌒☆


1. 포인터와 const

포인터와 관련해서 const는 약간 혼란스러울 수도 있습니다.

먼저 const int* const pw =&w; 이 한 줄을 보면 const가 2개나 있습니다.

하나는 포인터 자료형 앞에 하나는 포인터 변수 이름 앞에 하나씩 있습니다.

이것의 의미는 아래 그림을 보시면 쉽게 이해 가능합니다.

앞의 const는 포인터가 가리키는 변수의 값을 변경하려는 시도를 막고

뒤의 const는 포인터의 값을 변경하려는 시도를 막습니다.

const int* pw =&w; // *pw = 다른 값 -> 안됨

int* const pw =&w; // pw = 다른 주소 -> 안됨

2. 함수와 const

클래스와 함수에서도 const가 많이 사용됩니다.

함수의 호출에서 값에 의한 호출(call-by-value) 방법과

C++에서 참조에 의한 호출(call-by-reference) 방법이 있고

참조에 의한 호출이 훨씬 편리하다는 걸 제 블로그 여러 글에서 소개해 드렸습니다.

 

함수의 설계와 객체의 복사

전 책에서 이 부분을 좋아합니다. 왜냐하면 이 책을 읽지 않았더라면 평생 모르고 생각하지도 않고 살았을 테니까요. 같은 기능의 함수라도 매개변수와 반환형을 다르게 할 수 있고 그거에 따른

onesside-world.tistory.com

그런데, 사실 중요한 문제가 있습니다. 전달된 매개변수를 개발자가 부주의하게 수정할 수 있다는 것입니다.

 

아래와 같은 예시는 p의 매개변수들을 수정하는 게 아닌 비교하는 연산자 중복 함수인데

bool operator== (const Point &p) { return x == p.x && y == p.y; }

아래 예시처럼

bool operator== (Point &p) { p.x = 2; p.y = 3; }

같이 개발자의 부주의로 값을 수정해 버리는 문제가 발생할 수 있습니다.

 

컴파일러는 개발자의 실수를 알아차리지 못합니다.

따라서 전체 프로그램은 큰 오류를 발생할 수 있습니다.

참조에 의한 호출의 이러한 문제를 해결하기 위한 방법이 const입니다.

이러면 수정하는 경우 컴파일 오류를 발생하도록 하여 개발자의 실수를 방지하는 것입니다.

이러한 매개변수를 상수(const) 매개변수 또는 상수 참조자 매개변수라고 합니다.

 

다음은 유리스 클래스와 관련된 다양한 함수들에 const를 사용한 예입니다.

#include <iostream>
using namespace std;

class Rational
{
	int top, bottom;
public:
	Rational(int t = 0, int b = 1) : top(t), bottom(b) { }
	double real() const {
    		// real()을 상수 멤버 함수로 선언했기 때문에
    		// 클래스의 어떤 멤버 변수도 수정할 수 없습니다.
		// (1) top = 2; bottom += 10; 
		return (double)top / bottom;
	}
	friend ostream& operator<<(ostream& os, const Rational& f) {
        	// 매개변수 f를 상수로 선언하여 f를 수정할 수 없게 함
		// (2) f.bottom = 10;
		os << f.top << "/" << f.bottom;
		return os;
	}
	friend istream& operator >> (istream& is, Rational& f) /* const */ {
    		// 입력을 받기위해 f가 수정되어야 하기 때문에 const를 쓰면 안됨
            // friend 선언을 하여 일반 함수가 되었기 때문에 함수를 상수화 하면 안됨
            // 멤버 함수만 상수화 할 수 있음
		is >> f.top >> f.bottom;
		return is;
	}
	Rational operator+(const Rational& f) const {
    		// c = a + b같은 연산을 하는 덧셈 연산자 중복 함수
            // 그렇기 때문에 a와 b가 수정되면 안되기 때문에 const를 선언하여 변경되지 않도록 지정함
		// (4) top = 10; f.bottom = 20;
		return Rational(top*f.bottom + f.top*bottom, bottom*f.bottom);
	}
};
void main()
{
	Rational a(3, 4), b, c;
	const Rational h(1, 2);

	cout << "b 입력(a/b): ";
	cin >> b;
	// (5) cin >> h; // >> 중복 함수의 매개변수가 상수형이 아니기 때문에 안됨
	c = a + b;
	cout << " a  = " << a << endl;
	cout << " b  = " << b << endl;
	cout << " h  = " << h << endl; // << 중복 함수의 매개변수가 상수형이기 때문에 됨
	cout << "a+b = " << c << endl;
}

위의 예제에서 중요한게 나옵니다.

  1. 상수나 비상수 객체 모두 const 매개변수로 전달 할 수 있습니다. 
  2. 상수 객체를 비상수 매개변수로 전달하는 코드는 컴파일 에러를 발생시킵니다.

이 프로그램 같이 const를 가능한 많이 사용하는 것이 상대적으로 더 안전한 코드를 만드는 좋은 습관입니다.