상수 멤버 함수
상수 멤버 함수는 멤버값을 변경할 수 없는 함수이다. 멤버값을 단순히 읽기만 한다면 이 함수는 객체의 상태를 바꾸지 않는다는 의미로 상수 멤버 함수로 지정하는 것이 좋다. 클래스 선언문의 함수 원형 뒤쪽에 const 키워드를 붙이면 상수 멤버 함수가 된다. 함수의 앞쪽에서는 리턴값의 타입을 지정하기 때문에 const를 함수 뒤에 붙이는 좀 별난 표기법을 사용한다.
class Some
{
private:
int Value;
public:
int SetValue(int aValue); // 비상수 멤버 함수
int GetValue() const; // 상수 멤버 함수
};
정수형의 Value 변수가 비공개 영역에 선언되어 있고 이 멤버값을 읽고 쓰는 Get/Set 액세스 함수들은 공개 영역에 선언되어 있다. Value를 외부에서 변경하고 싶다면 SetValue 함수를 호출하고 Value를 읽고 싶을 때는 GetValue 함수를 호출한다. 이때 GetValue는 객체의 어떠한 멤버값도 변경하지 않으므로 상수 멤버 함수이며 이 함수 원형 뒤에 const를 붙여 GetValue는 값을 읽기만 한다는 것을 컴파일러에게 확실하게 알려 준다.
상수로 선언된 객체에 대해서는 상수 멤버 함수만 호출할 수 있으며 비상수 멤버 함수는 호출할 수 없다. 왜냐하면 상수 객체는 읽기 전용이므로 어떤 멤버의 값도 변경되어서는 안되기 때문이다. 다음 예제를 보자.
예 제 : ConstFunc
|
#include <Turboc.h>
class Position
{
private:
int x,y;
char ch;
public:
Position(int ax, int ay, char ach) { x=ax;y=ay;ch=ach; }
void OutPosition() const { gotoxy(x, y);putch(ch); }
void MoveTo(int ax, int ay) { x=ax; y=ay; }
};
void main()
{
Position Here(1,2,'A');
Here.MoveTo(20,5);
Here.OutPosition();
const Position There(3,4,'B');
There.MoveTo(40,10); // 에러 발생
There.OutPosition();
}
문자를 출력하는 OutPosition함수는 값을 읽기만 하므로 const로 선언되어 있고 MoveTo함수는 위치를 옮기기 위해 x, y 멤버의 값을 변경하므로 const가 아니다. 만약 MoveTo를 const로 지정하면 상수를 변경할 수 없다는 에러로 처리된다. 객체의 값을 조금이라도 변경하는 함수는 상수 멤버 함수로 지정하지 말아야 한다. const로 선언된 OutPosition에 x++따위의 코드를 작성하면 상수 멤버 함수가 객체의 상태를 변경하려고 했으므로 역시 에러로 처리될 것이다. 단, 상수 멤버 함수라도 정적 멤버 변수의 값은 변경할 수 있는데 정적 멤버는 객체의 소속이 아니며 객체의 상태를 나타내지도 않기 때문이다.
main의 테스트 코드를 보자. Here는 비상수 객체로 선언되었므로 OutPosition으로 문자를 출력함은 물론 MoveTo로 위치를 옮길 수도 있다. 그러나 There는 상수 객체로 선언되었므로 상수 멤버 함수인 OutPosition만 호출할 수 있으며 MoveTo 호출문은 에러로 처리된다. 이 문장이 에러로 처리되는 이유는 다음 문장이 에러로 처리되는 이유와 동일하다.
const int i=5;
i=8; // 에러
상수에 어떤 값을 대입하여 변경할 수 없는 것과 마찬가지로 상수 객체의 상태를 변경하는 함수를 호출하는 것도 불가능하다. 비상수 멤버 함수가 받는 객체 포인터 this는 Position * const 형이며 this 자체는 상수이지만 this가 가리키는 대상은 상수가 아니다. 반면 상수 멤버 함수가 받는 객체 포인터 this는 const Position * const 형이며 this도 상수이고 this가 가리키는 대상도 상수이다. 결국 상수 멤버 함수의 제일 끝에 붙는 const는 이 함수로 전달되는 숨겨진 인수 this의 상수성을 지정한다.
컴파일러는 멤버 함수의 코드를 읽어보고 멤버값을 변경하는지 아닌지를 정확하게 판단할 수 없다. 멤버의 값을 변경하는 방법에는 직접적인 대입만 있는 것이 아니라 포인터를 통한 간접 변경, 함수 호출을 통한 변경 등 여러 가지 변칙적인 방법들이 많기 때문에 함수의 내용만으로 상수성을 정확하게 판단하는 것은 불가능하다. 그래서 상수 멤버 함수인지 아닌지는 개발자가 판단해서 지정해야 한다. 만약 OutPosition의 원형에 const를 빼 버리면 There.OutPosition() 호출조차도 에러로 처리된다. 왜냐하면 컴파일러는 OutPosition함수가 멤버값을 변경할 수도 있다고 생각하기 때문이다.
어떤 멤버 함수가 값을 읽기만 하고 바꾸지는 않는다면 const를 붙이는 것이 원칙이며 이 원칙대로 클래스를 작성해야 한다. 그러나 이 책의 예제들은 예제로서의 간략함을 위해 종종 이 원칙을 무시하고 있는데 절대로 본받지 말아야 한다. 예제는 다루고 있는 주제의 핵심을 보여야 하기 때문에 불가피하게 모든 원칙을 준수하기 어렵다. 만약 원칙을 어기면 상수 객체에 대해서 비상수 멤버 함수를 호출할 수 없게 된다. 다음과 같은 함수의 경우를 보자.
void func(const Position *Pos);
이 함수로 전달되는 Pos는 상수 지시 포인터이므로 *Pos는 func 함수 안에서 상수 객체이다. 따라서 Pos 객체에 대해서는 상수 멤버 함수만 호출할 수 있다. MoveTo로 위치를 옮길 수 없으며 OutPosition 이 상수 멤버 함수로 지정되어 있지 않다면 문자를 출력하는 것도 불가능해진다. 이런 경우에도 잘 동작하려면 원칙대로 멤버값을 바꾸지 않는 함수는 상수 멤버 함수로 지정해야 한다.
함수의 상수성은 함수 원형의 일부로 포함된다. 그래서 이름과 인수 목록이 같더라도 const가 있는 함수와 그렇지 않은 함수를 오버로딩할 수 있다. 즉, 다음 두 함수는 이름과 취하는 인수가 같더라도 다른 함수로 인식된다.
void func(int a, double b, char c) const;
void func(int a, double b, char c);
사실 이는 지극히 당연한 규칙인데 인수의 상수성이 오버로딩 조건이 되므로 const인 this와 그렇지 않은 this를 받는 함수도 당연히 중복 정의할 수 있다. 다음 두 함수가 중복 정의 가능한 것과 같은 이유라고 이해하면 된다.
void func(const char *p);
void func(char *p);
컴파일러는 상수 객체에 대해서는 위쪽의 상수 멤버 함수를 호출할 것이고 그렇지 않은 경우는 아래쪽의 비상수 멤버 함수를 호출할 것이다. 객체가 상수일 때와 그렇지 않을 때의 처리를 다르게 하고 싶다면 두 타입의 함수를 따로 제공하는 것도 가능하다. const와 비슷한 지정자인 volatile 도 마찬가지로 함수 원형의 일부이다.
나름 결론 :
1. 함수의 const 도 오버로딩 된다.
2. 상수 멤버 함수는 상수 객체나 비상수 객체에서 부를 수 있지만, 상수 객체는 비상수 멤버 함수는 부를 수 없다.
3. 값에 의한 전달 보다는 상수 객체 참조자에 의한 전달 방식이 낫다. ( 상수 객체를 사용하는데, 상수 멤버 함수만 호출 할 수 있기 때문에, 상수 멤버 함수를 사용하는 듯 )
댓글 없음:
댓글 쓰기