아이템 3 : 낌새만 보이면 const를 들이대 보자
- Const 는 상수 값을 선언하는 데 사용된다. 여러 가지 용도로 사용되는데,
- 상수 포인터
- STL 의
const_iterator
- 함수 선언 - 함수 반환 값을 상수로 정해 주면, 안전성이나 효율을 포기하지 않고도 사용자측의 에러 돌발 상황을 줄이는 효과를 꽤 자주 볼 수 있게 된다.
1
2
3
4
5
6
7
8
9
10
| class Rational {};
const Rational operator* (const Rational& lhs, const Rational& rhs); //반환 값을 const 로 선언한 이유는
void DoSomething() {
Rational a, b, c;
(a * b) = c; //여기서 a*b의 결과에 두고 operator = 을 호출하는 것을 막기 위해
//const 라서 값 변경 불가능
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class TextBlock {
public:
TextBlock(std::string str) : text(str) {}
const char& operator[](std::size_t position) const { return text[position]; } //상수 객체에 대한 operator[]
char & operator[](std::size_t position) { return text[position]; } //비상수 객체에 대한 operator[]
private:
std::string text;
};
void DoSomething2() {
TextBlock tb("Hello");
std::cout << tb[0]; //비상수 멤버 호출
const TextBlock ctb("World");
std::cout << ctb[0]; //상수 멤버 호출
tb[0] = 'x'; //문제 없음
ctb[0] = 'x'; // 컴파일 에러
}
|
- 상수 멤버 함수 : 멤버 함수에 붙는 const 키워드는 "해당 멤버 함수가 상수 객체에 대해 호출될 함수이다" 라는 사실을 알려준다. 클래스의 인터페이스를 이해하기 좋게 하기 위해,(변경할 수 있는 것과 없는 것을 알려준다) 그리고 이 키워드를 통해 상수 객체를 사용할 수 있게 하자는 것. 두 가지 중요한 개념이 있는데
- 비트수준 상수성bitwise
constness : 다른 말로 물리적 상수성이라고도 한다. 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야(정적 멤버 제외) 그 멤버 함수가 'const' 임을 인정하는 개념. 즉 그 객체를 구성하는 비트들 중 어떤 것도 바꾸면 안 된다는 것.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class CTextBlock {
public:
CTextBlock(char * str) : pText(str) {}
char & operator[] (std::size_t position) const { return pText[position]; } //부적절한
//(그러나 비트수준 상수성이 있어서 허용되는)
//operator[] 의 선언
private:
char *pText;
};
void DoSomething3() {
const CTextBlock cctb((char*)"Hello"); //상수 객체 선언
char *pc = &cctb[0]; //상수 버전의 operator[]를 호출하여 cctb의
//내부 데이터에 대한 포인터를 얻습니다.
*pc = 'j'; //cctb는 이제 jello 라는 값을 갖습니다. -> 분명 상수 객체임에도 내부 멤버 변수가 변경되었다.
}
|
- 논리적 상수성이란 이런 황당한 상황을 보완하는 개념으로 등장하였다. 상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class CTextBlock {
public:
CTextBlock(char * str) : pText(str) {}
char & operator[] (std::size_t position) const { return pText[position]; }
std::size_t Length() const;
private:
char *pText;
mutable std::size_t textLength; //이 데이터 멤버들은 어떤 순간에도 수정 가능
mutable bool lengthIsValid; //심지어 상수 멤버함수 안이라도 가능
};
std::size_t CTextBlock::Length() const {
if (!lengthIsValid) {
textLength = std::strlen(pText); //상수 멤버 함수안에서는 일반적으로 안되지만
lengthIsValid = true; //mutable 키워드가 붙어서 가능
}
return textLength;
}
|
- 상수 멤버 및 비상수 멤버 함수에서 코드 중복을 피하는 방법.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class TextBlock {
public:
TextBlock(std::string str) : text(str) {}
const char& operator[](std::size_t position) const { return text[position]; }
char & operator[] (std::size_t position) { //코드 중복 대처 요령(비상수 버전이 상수 버전 호출)
return const_cast<char&>( //c++ 에서 캐스팅은 썩 좋지 못한 아이디어지만, 코드 중복도 쉽게 넘길 순 없다.
static_cast<const TextBlock&>(*this)[position] //상수 버전 operator[]를 호출, *this에 const를 붙이고, 반환 타입에서 const를 뗀다.
); //operator[]속에서 operator[] 를 호출하면 재귀적으로 계속 호출하게 되니,
} //const operator[]를 호출하기 위한 방법으로 *this 에 const 를 붙인다.(탁월한 방법은 아니지만 차선책)
private:
std::string text;
};
|
댓글
댓글 쓰기