[C++] 항목 3: 낌새만 보이면 const를 들이대 보자!
카테고리: cpp
태그: cpp
이 글은 아래의 책을 정리하였습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 1. C++에 왔으면 C++의 법을 따릅시다Permalink
👉🏻 항목 3: 낌새만 보이면 const를 들이대 보자!Permalink
1. const 키워드Permalink
char greeting[] = "Hello";
// 1. 데이터 수정 O, 포인터 수정 O
char * p = greeting;
// 2. 포인터 수정 O, 데이터 수정 X
const char * p = greeting;
// 3. 포인터 수정 X, 데이터 수정 O
char * const p = greeting;
// 4. 포인터 수정 X, 데이터 수정 X
const char * const p = greeting;
const가 *의 앞에 있는가, 뒤에 있는가? 에 따라 의미가 달라진다.
// 아래 두 함수는 똑같다.
// 상수 Widget 객체를 가진다.
void f1(const Widget * pw);
void f2(Widget const * pw);
또한 const와 타입의 위치는 상관이 없다.
즉, 위의 f1과 f2는 같은 함수이다.
using namespace std;
vector<int> vec;
// T * const처럼 동작한다.
const vector<int>::iterator iter = vec.begin();
*iter = 10; // O, 데이터 수정 가능
++iter; // X, 주소 수정 불가능
// const * T처럼 동작한다.
vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; // X, 데이터 수정 불가능
++cIter; // O, 주소 수정 가능
반복자에서 const를 사용하고 싶다면, 위의 방법을 사용해야 한다.
class Rational { ... };
// const 반환값, const 매개변수
const Rational Operator*(const Rational& lhs, const Rational& rhs);
Rational a,b,c;
(a*b) = c; // 에러!
if(a*b = c); // 에러!
const 키워드는 함수에서도 빛을 발한다.
const가 포함된 결과 값에 대입을 하는 어처구니 없는 실수를 예방할 수 있다.
1.1. 상수 멤버 함수Permalink
class TextBlock {
public:
const char& operator[](size_t position) const {
return text[position];
}
char& operator[](size_t position) {
return text[position];
}
private:
string text;
}
const char& operator const { … }
위의 멤버 함수에서 const가 끝에 붙은 것을 확인할 수 있다.
이것은 “해당 멤버 함수가 상수 객체에 대해 호출된 함수”라는 것을 알려준다.
아래와 같은 코드에 해당한다.
const TextBlock ctb(”World”); → 이것은 상수 객체로 선언되었다.
cout « ctb[0];
이를 통해 얻을 수 있는 장점은 두가지가 있다.
1. 클래스의 인터페이스를 이해하기 좋아진다.
→ 클래스로 만들어진 ‘객체를 변경할 수 있는 함수’와 ‘변경할 수 없는 함수’의 구분을
사용자 측에서 하기가 쉬워진다.
2. 상수 객체를 사용할 수 있게 된다.
→ 코드의 성능을 높이기 위해선 객체 전달을
‘상수 객체에 대한 참조자(reference-to-const)’로 진행해야 한다.
위와 같은 장점을 얻을 수 있기에 알아두고 사용하는 것이 좋다.
1.2. const 멤버 함수 오버로딩Permalink
class TextBlock {
public:
// 1번
const char& operator[](size_t position) const {
return text[position];
}
// 2번
char& operator[](size_t position) {
return text[position];
}
private:
string text;
}
위에서 언급했던 이 코드에서 두 멤버 함수는 const의 차이만 있다.
이러한 멤버 함수들은 오버로딩이 가능하다.
TextBlock tb("Hello");
cout << tb[0]; // 1번 멤버 함수 호출
const TextBlock ctb("World");
cout << ctb[0]; // 2번 멤버 함수 호출
객체를 생성한 뒤, 위와 같이 사용하면 각자 다른 멤버 함수를 호출할 수 있다.
상수 객체가 생기는 경우는 다음과 같다.
- 상수 객체에 대한 포인터로 객체가 전달
- 상수 객체에 대한 참조자로 객체가 전달
cout << tb[0]; // O tb[0] = 'x'; // O cout << ctb[0]; // O ctb[0] = 'x'; // X, ctb 객체에 쓰기는 불가능하다.
여기서 눈여겨 보아야 할 점들이 있다.
- 두번째 줄에서 tb[0] = ‘x’는 tb.text[0]의 사본에 값을 대입하는 것이므로 실질적으로 수정되지 않는다.
- 네번째 줄에서 발생한 에러는 operator[]의 반환 타입이 const이기 때문에 생긴 에러이다.
1.3 비트수준(물리적) 상수성과 논리적 상수성Permalink
비트수준 상수성: 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 ‘const’임을 인정한다.
논리적 상수성: 일부 몇 비트 정도는 수정할 수 있되, 그것을 사용자 측에서 알아채지 못하면 된다.
비트수준 상속성은 문제가 있다.
멤버함수 내에서는 변수가 수정되지 않지만, 외부에서 수정되는 경우가 생길 수 있다.
class CTextBlock {
public:
char& operator[](size_t position) const {
return pText[position];
}
private:
char* pText;
};
const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';
위의 코드가 그 예시로 함수의 결과 값이 주소이기에, 주소가 가리키는 값을 수정하고 있다.
이런 상황을 보완하기 위해 나온 개념이 논리적 상수성이다.
1.4. mutable 키워드Permalink
class CTextBlock {
public:
size_t length() const;
private:
char* pText;
size_t textLength;
bool lengthIsValid;
};
size_t CTextBlock::length() const {
if(!lengthIsValid) {
textLength = strlen(pText); // 에러!
lengthIsValid = true; // 에러!
}
return textLength;
}
위의 코드는 비트수준 상수성에도 맞지 않으며, const가 포함된 멤버 함수 내에서
변수를 수정하려하고 있으므로, 에러가 난다.
이런 상황을 위해 나온 것이 mutable 키워드이다.
...
mutable size_t textLength;
mutable bool lengthIsValid;
...
변수를 선언할 때, 앞에 mutable만 붙여주면 const의 제한에서 예외 대우를 받게 된다.
1.5. 코드 중복 제거Permalink
class TextBlock {
public:
const char& operator[](size_t position) const {
...
return text[position];
}
char& operator[](size_t position) {
return const_cast<char&>( // const를 떼어냄
static_cast<const TextBlock&> // const를 붙임
(*this)[position] // operator[]의 상수 버전 호출
);
}
};
const가 붙고 안붙고 차이만 있는 함수를 만들려고 한다.
이를 만들기 위해 중복된 코드를 두번 사용하지 않는 방법도 있다.
- const 키워드를 붙인다.
- operator의 상수 멤버함수를 호출한다.
- const 키워드를 뗀다.
댓글남기기