사용자:Darksoldier
위키백과 ― 우리 모두의 백과사전.
2000.04.29
1. 상수를 선언할 때 사용되는 const 2. const 변수가 compile 후 link될 때.
2.1. C파일에서의 const 2.2. CPP파일에서의 const 2.3. C++에서 extern link를 허용한 const 2.4. 포인터의 경우.
3. 클래스의 멤버 함수가 const 속성을 가질때 4. 클래스의 멤버 함수가 const일 때 멤버의 변경
4.1. const_cast를 사용한다. 4.2. mutable을 사용한다. 4.3. 딴얘기...
5. overloading과 const
첫번째는 const에 대해서 살펴보도록 하겠습니다.
const는 C++을 C보다 안전한 언어로 만들어주는 훌륭한 기능입니다. C에도 const는 있었지만 C++에서는 그 기능이 보다 막강해졌습니다.
예로 가볍게...
const int max=10; int array[max+1];
이런게 C에선 불가능하지만 C++에서는 가능합니다. const란 값이 변하지 않는 상수나 클래스의 멤버 변수를 변경하지 않는 멤버 함수를 선언할 때 사용하는 키워드입니다. 상수의 경우 #define으로 선언할 수도 있습니다만... 여러 책들을 보면 const를 추 천합니다. (-_-;) 뭐... 디버깅할 때 값을 볼 수 있다거나, 그 외에도 좋다고 하는데... 가지고 있는 C++책을 보시면 이런저런 설명이 나와있을 겁니다.
1. 상수를 선언할 때 사용되는 const 일반적인 상수를 선언할 때는
const int a = 10;
이런 식으로 사용하겠죠. const 변수는 값이 변할 수 없으므로 위와 같이 변수를 선언하면서 초기화를 해줘야 합니다. const 변수는 컴파일러가 상황에 따라서 실제로 할당하는 메모리가 바뀔 수 있습니다.
포인터에 대해 const를 사용할 때는 좀 복잡합니다. 1) const int* p1; 2) int const* p2; 3) int* const p3; 1), 2)는 int의 값을 바꿀 수 없다는 뜻이고, 3)은 포인터의 값을 바꿀 수 없다는 뜻이죠. 따라서 1), 2)는 옳은 선언이지만 3)과 같이 선언할 수는 없습니다. 선언하면서 동시에 p3이 가리키는 주소를 넣어줘서 초기화를 해줘야겠죠.
클래스의 멤버 변수에 대해서 const를 사용할 때는 그 멤버 변수의 값을 바꿀 수 없다는 뜻이 되겠죠. const 멤버 변수도 초기화를 해야겠죠? const 변수는 그 값이 바뀔 수 없으므로 초기화는 생성자를 들어가기 전에 해야 합니다. 다음과 같이 하면 되겠죠.
class A {
// ... const int m_nNumber;
public:
A(); // ctor // ...
};
A::A() : m_nNumber(100) {
// ...
}
하지만 여기서 생각해볼게 있습니다. 위와같은 식으로 사용되는 const 변수가 각 개체마다 생기는게 의미가 있을까요? 어차피 이 클래스에 항상 같은 값이 적용되는 상수라면 그럴 필요가 없겠죠. 따라서 정수라면 enum을 사용해서 값을 정의하는게 편하고, 다른 타입이라면 static const 속성을 가지도록 해주는게 효율적입니다. static 변수를 초기화 하는 법은 아시죠? const가 붙어도 마찬가지입니다. 나중에 static 얘기할 때 보도록 하죠.
각 개체마다 고유한 값을 가지는 상수라면(그 개체의 아이디라거나) 위와 같이 선언해주면서 생성자의 초기화리스트에서 값을 초기화해주면 되겠죠.
1은 다 아시는 내용이죠?
2. const 변수가 compile 후 link될 때. 그럼 const 변수가 link를 할 때 어떻게 되는지 알아보겠습니다. 이게 좀 재밌는데... C에서는 const 변수가 기본적으로 external link를 하지만 C++에서는 internal link를 하도록 변경되었습니다. 왜그랬는지는 잘 모르겠습니다만 제가 보기엔 헤더 파일에 const를 이용한 상수를 선언하기 편하게 하려고 그런 것 같습니다. C에서도 const가 있었지만 const 변수가 external이었기 때문에 쓰기 귀찮아서 #define을 주로 사용했다는 생각이 드네요.
예를 들어...
2.1. C파일에서의 const [t1.c] const int cnVar = 1;
[t2.c]
const int cnVar = 2;
이렇게 되어있으면 링크시 중복 선언됐다고 에러가 나지만. 각 파일이 t1.cpp, t2.cpp일 경우에는 아무 문제없습니다. 즉,
2.2. CPP파일에서의 const [t1.cpp] const int cnVar = 1;
[t2.cpp]
const int cnVar = 2;
이건 괜찮다는 거죠. 즉 static 변수처럼 동작합니다. 그리고 cnVar는 각 파일에서 1, 2의 값을 가집니다.
2.3. C++에서 extern link를 허용한 const [t1.cpp] extern const int cnVar = 1;
[t2.cpp]
extern const int cnVar;
이 경우에는 두 파일에서 모두 cnVar를 사용할 수 있으며 그 값은 1이 됩니다. (t1에는 extern키워드를 사용하지 않아도 됩니다.)
2.4. 포인터의 경우. /extern을 봐주세요.
3. 클래스의 멤버 함수가 const 속성을 가질때 멤버 함수를 선언하면서 뒤에 const를 붙이는 것은 그 함수가 클래스의 상태를 변경하지 않는다는 뜻입니다. 쉽게 생각하면 멤버 변수의 값을 변경하지 않겠다는 선언이죠.
MFC라면 AssertValid 뒤에 const가 붙어있는걸 보셨을 겁니다. 이 함수를 호출해도 클래스에 아무 변화가 없다는 뜻이죠.
class B {
int m_nNumber;
public:
B::B(int nNum) : m_nNumber(nNum) {}
int GetNumber() const // (a) { return m_nNumber; }
int GetNumberError() const; // (b)
void SetNumber(int nNewValue) // (c) { m_nNumber = nNewValue; }
void SetNumberError(int nNewValue) const // (d) { m_nNumber = nNewValue; }
};
int B::GetNumberError() // (b) {
return m_nNumber;
}
(a)와 (c)는 문제없는 함수입니다. 다 아시죠? (b)는 에러가 납니다. 컴파일러는 선언한 함수와 실제 구현된 함수가 다르다고 생각합니다. (d)도 에러가 납니다. 이거야 말로 const함수 내부에서 멤버의 값을 바꾸려고 해서 생기는 에러입니다. (이게 잘못된 사용의 옳은 예입니다. (b)는 참고하시라고 올린겁니다.)
무슨 뜻인지 아시겠죠?
const 멤버 함수는 선언할 때 컴파일러가 체크해서 프로그램을 보다 안전하게 하는 역할을 합니다. 그리고 안정성을 주는 것 외에도 필요합니다. const로 선언된 개체에서는 const 멤버 함수만 호출할 수 있죠.
ex)
const B ex(1); // m_nNumber는 어찌어찌 초기화가 됐다고 치죠. int b = ex.GetNumber(); // 이건 잘 됩니다. ex.SetNumber(5); // 이건 호출하면 에러납니다.
4. 클래스의 멤버 함수가 const일 때 멤버의 변경 뭐... 살다보면 별별 일이 생길 수도 있습니다. const 함수인데 멤버 변수의 값을 바꿔야 하는 일도 생길 수 있겠죠? C++ Programming Language에 나온 예제가 멋지니까 그대로 베끼겠습니다. (단 이럴 때도 제 맘대로 헝가리안 표기법을 씁니다. 변수 이름은 마구 바뀝니다.)
class CDate {
bool m_bCacheValid; string m_strCache; void ComputeCacheValue(); // fill cache // ...
public:
// ... string StringRep() const; // string representation
};
요지를 설명하자면 CDate란 클래스는 날짜를 표시하는 클래스입니다. 날짜를 문자열로 표시하고 싶어질 때 StringRep 함수를 호출하면 CDate의 개체값이 표시하는 날짜를 문자열로 얻을 수 있습니다. 그런데 StringRep를 호출할 때마다 문자열로 변환하는 작업을 하게 된다면 비효율적일테니까 날짜를 문자열로 바꿔서 m_strCache에 저장해둡니다. m_bCacheValid는 m_strCache가 개체가 가리키는 날짜를 표 시하고 있는지 여부를 표시합니다. 즉, StringRep을 부르면 true가 되고, 그 다음에 개체의 값이 바뀌면 false가 되겠죠.
클래스를 사용하는 프로그래머의 입장에서 보면 StringRep 함수는 실제 날짜의 속성을 바꾸는 함수가 아니니까 const가 붙는게 당연합니다. 하지만 실제로 StringRep 함수는 m_strCache에 개체의 날짜 를 넣고, m_bCacheValid를 true로 바꿔줘야 합니다. 어떻게 구현하면 될까요? 이런 상황을 logical constness하고 한다고 하네요.
4.1. const_cast를 사용한다. string CDate::StringRep() const {
if (false == m_bCacheValid) { // Tip-1. 제일 아래를 보세요. CDate* pThis = const_cast< CDate*>(this); // const 속성을 제거. Tip-2. pThis->ComputeCacheValue(); pThis->m_bCacheValid = true; } return m_strCache;
}
자기 자신의 const속성을 제거한 포인터 변수를 선언해서 그 변수에 대해서 동작하는겁니다. 이 방법은 보기에도 별로고 (아름답지 않습니다. -_-;) 개체 자체가 const일 때는 어떻게 동작할지 보장할 수 없답니다.
CDate d1; const CDate d2; string s1 = d1.StringRep(); string s2 = d2.StringRep(); // undefined behavior
실제로 컴파일은 별 문제없이 됩니다. 다만 컴파일러가 const 변수의 경우 실제로 메모리의 값을 변경할 수 없는 보호장치를 해둔다면 어떤 일이 일어날지 알 수 없다는 겁니다.
4.2. mutable을 사용한다. mutable은 바로 이럴 때를 위해 있는 키워드입니다. "얜 const가 아냐" 라는 뜻입니다. 그 멤버 변수를 포함하는 개체가 const일 경우에도 적용됩니다. 그리고 mutable을 선언한 변수는 static이나 const 속 성을 가지고 있지 않아야됩니다.
class CDate {
mutable bool m_bCacheValid; mutable string m_strCache; void ComputeCacheValue() const; // fill (mutable) cache // ...
public:
// ... string StringRep() const; // string representation
};
string CDate::StringRep() const {
if (!m_bCacheValid) { // 책에 위와 아래를 다르게 해놨네요. 뭐 뭐든 어떻습니까. ComputeCacheValue(); m_bCacheValid = true; } return m_strCache;
}
CDate d1; const CDate d2; string s1 = d1.StringRep(); string s2 = d2.StringRep(); // 얘도 잘 됩니다.
4.3. 딴얘기... 책에서는 struct cache라는 구조체를 만들어서 m_bCacheValid와 m_strCache를 따로 보관하는 방법을 마지막으로 보여주고 있습니다. 구현하기 어렵지 않으실겁니다. 포인터 변수 하나 만들고 생성자에서 new하고 소멸자에서 delete 해주면 되겠죠?
5. overloading과 const const속성이 사용되는 함수와 그렇지 않은 함수를 같이 제공해서 사용을 편하게 해줄 수 있습니다.
class C {
// ...
};
class D {
C m_c; // ...
public:
// ... C& GetC() { return m_c; } const C& GetC() const { return m_c; }
};
이렇게 하면 const인 D의 개체에서 GetC를 부르면 const C&를 얻을 수 있습니다.
D d1; const D d2; C& c1 = d1.GetC(); C& c2 = d2.GetC(); // error! const C& c3 = d1.GetC(); const C& c4 = d2.GetC();
Tip-1. 강좌와는 직접적인 연관은 없는 얘기입니다만... 유용한 팁이 될 것 같아서 쓰겠습니다. 원래 책은 반대로 되어있습니다. 저도 보통은 반대로 적습니다. (그래도 이런데 쓸 때는 저렇게 써보고 싶었습니다.) 상수를 앞에 쓰는 습관을 생활화하면 == 를 쓸 자리에 =를 썼을 때 에러가 나기 때문에 실수를 줄일 수 있다고 합니다. 참고로 말씀드리자면, VC++에서는 warning level을 4로 올리면 if나 while등에서 =를 쓸 때 경고메시지가 나옵니다. warning level은 항상 4로 놓고 하세요. 이 외에도 여러가지 좋은 경고를 보여줍니다. 의도한 문장일 경우에도 경고가 나오는게 귀찮으면 다음과 같이 쓰세요.
if ( (nNewVal=nOldVal) != 0 ) { .... }
Tip-2. const_cast는 원래의 C++에는 없던 키워드입니다. static_cast, dynamic_cast, reinterpret_cast, const_cast, typeid, type_info등의 키워드가 추가됐죠. const_cast는 const 속성을 제거할 때 사용하는 cast입니다. 어쩌면 다음글의 주제가 const_cast 를 비롯한 이 키워드들...(강의가 알파벳순인가...?)