센로그
[EC++] 3. 낌새만 보이면 const를 들이대 보자! 본문
const 키워드의 장점
- 가독성: 값을 변경하지 않겠다는 제작자의 의도를 컴파일러 및 다른 프로그래머들과 나눌 수 있다.
- 안정성 및 오류 방지: 변수 값의 실수로 인한 변경 방지
- 함수에서 사용하는 것도 좋음
- 상수 멤버함수: const와 non const 함수 오버로딩 지원
- 즉 같은 이름으로 r/w 둘다 되게, 또는 r만 되게 만들 수 있음.
- 반환값 및 매개변수도 상수화 가능
- 상수 멤버함수: const와 non const 함수 오버로딩 지원
상수 포인터 vs 상수 객체를 가리키는 포인터
- 쉽게 구분하는 법!
- *와 포인터변수명 사이에 const가 붙은 경우만 상수 포인터를 의미함. 나머지는 다 상수 객체.
STL const iterator vs const_iterator
std::vector<int> vec = {1, 2, 3};
std::vector<int>::iterator const it1 = vec.begin(); // 상수 반복자 (const iterator)
*it1 = 10; // 반복자가 가리키는 값을 수정할 수 있음
// it1++; // 오류: 반복자를 변경할 수 없음
std::vector<int>::const_iterator it2 = vec.begin(); // 상수 반복자에서 가리키는 값이 상수 (const_iterator)
// *it2 = 10; // 오류: 반복자가 가리키는 값을 수정할 수 없음
it2++; // 반복자 자체는 이동 가능
함수에서의 const
1. 반환값의 const
class MyClass {
public:
MyClass operator+(const MyClass& other) const {
MyClass result;
// 두 객체의 합을 계산하여 result에 저장
return result;
}
};
const MyClass a, b;
(a + b) = MyClass(); // 오류: const 반환값은 대입 불가
- 반환값에 대입하려고 하는 실수 방지 가능.
2. 매개변수의 const
- 원래 객체가 수정되지 않음을 보장
3. const 멤버 함수
- const 객체인 경우, const 멤버함수만 호출이 가능하다.
- const, non-const 멤버함수의 오버로딩이 가능하다.
class TextBlock {
public:
// 상수 객체에 대한 operator
const char& operator[](std::size_t position) const
{
return text[position];
}
// 비상수 객체에 대한 operator
char& operator[](std::size_t position)
{
return text[position];
}
private:
std::string text;
};
- const TextBlock 객체인 경우 위의 연산자가, 일반 TextBlock 객체인 경우 아래의 연산자가 호출된다.
- 둘다 read 로직은 잘 된다.
- const 객체의 write의 경우, const 연산자의 반환값에 붙은 const로 인해 write는 안된다.
어떤 멤버 함수가 상수 멤버라는 것은 어떤 의미일까?
- 비트 수준 상수성 (물리적 상수성)
- 그 객체의 어떤 멤버 변수도 변경하지 않는다. (read만 하는 건 가능)
- 한 비트도 건드리지 않으며, 이를 컴파일러가 검사한다.
- 그러나 포인터 개념이 들어가는 순간 작동이 달라질 수 있다.
- 그 객체의 어떤 멤버 변수도 변경하지 않는다. (read만 하는 건 가능)
class CTextBlock {
public:
char& operator[](std::size_t position) const // 상수 멤버함수
{
return pText[position]; // pText를 write하지는 않으므로 이렇게 사용 가능
}
private:
char* pText;
};
- 이 경우, 다음과 같은 코드가 작동이 가능하다.
const CTextBlock cctb("Hello"); // 상수 객체 cctb를 만든다.
char *pc = &cctb[0];
*pc = 'J'; // cctb는 Jello가 된다.
- 이런 예기치 못한 코드가 동작이 되어버린다.
- 논리적 상수성
- 객체를 전체 다 못건드리게 하는 것보다는, 객체의 논리적 상태를 유지하는 한, 이에 영향을 주지 않는 멤버는 수정할 수 있게 하는 것.
- 대표적으로 캐시를 위한 함수.
- mutable 키워드를 사용한 멤버의 경우 상수 멤버 함수에서도 수정할 수 있다.
- 객체를 전체 다 못건드리게 하는 것보다는, 객체의 논리적 상태를 유지하는 한, 이에 영향을 주지 않는 멤버는 수정할 수 있게 하는 것.
class Data {
public:
Data(int v) : value(v) {}
int getValue() const {
if (!isCached) {
cachedValue = value * 2; // 논리적 상태와 무관한 내부 캐시 수정
isCached = true;
}
accessCount++; // 접근 횟수 기록
return cachedValue;
}
private:
int value;
mutable int cachedValue = 0; // mutable로 선언된 캐시 변수
mutable bool isCached = false; // mutable로 선언된 캐시 상태 변수
mutable int accessCount = 0; // mutable로 선언된 접근 횟수 기록 변수
};
const, non const 멤버 함수의 코드 중복을 피하는 방법
- 상수, 비상수 멤버함수 간에 코드 중복이 심한 경우, 이를 피하는 방법이다.
- 오버로딩을 통해 비상수 객체가 상수 멤버 함수를 호출하도록 만들면 된다.
- 당연히, 반대로 하는 건 안전하지 않다. 상수 객체가 변경될 위험이 생기기 때문이다.
class TextBlock {
public:
// 상수 객체에 대한 operator
const char& operator[](std::size_t position) const
{
return text[position];
}
// 비상수 객체에 대한 operator
char& operator[](std::size_t position)
{
return
// 2. 상수성을 다시 제거하여 반환
const_cast<char&>(
// 1. 상수 객체로 캐스팅하여 상수 객체에 대한 연산자 호출
static_cast<const TextBlock&>(*this)[position]
);
}
private:
std::string text;
};
- static_cast<const TextBlock&>(*this)[position]를 통해 비상수 객체 자체를 상수 객체로 캐스팅하여, 상수 객체에 대한 []연산자를 호출한다.
- 이후 const_cast<char&>를 통해 다시 상수성을 제거하고 반환한다.
+) 예제 코드
#include <iostream>
class TextBlock {
public:
TextBlock(std::string str) {
text = str;
}
// 상수 객체에 대한 operator
const char& operator[](std::size_t position) const
{
return text[position];
}
// 비상수 객체에 대한 operator
char& operator[](std::size_t position)
{
return
// 2. 상수성을 다시 제거하여 반환
const_cast<char&>(
// 1. 상수 객체로 캐스팅하여 상수 객체에 대한 연산자 호출
static_cast<const TextBlock&>(*this)[position]
);
}
private:
std::string text;
};
int main() {
const TextBlock ctb("Hello");
TextBlock tb("Hello");
// ctb[0] = 'J'; // 불가능
tb[0] = 'J'; // 가능
return 0;
}
'Effective > Effective C++' 카테고리의 다른 글
[EC++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (2) | 2024.11.13 |
---|---|
[EC++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (2) | 2024.11.13 |
[EC++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2024.11.13 |
[EC++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자 (0) | 2024.11.12 |
[EC++] 1. C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2024.11.12 |
Comments