이펙티브 C++
https://www.yes24.com/Product/Goods/17525589
페이지 61p ~ 70p
요약
항목3 낌새만 보이면 const를 들이대 보자!
상수 멤버 함수
- 멤버 함수에 붙는
const
키워드의 역할은해당 멤버 함수가 상수 객체에 대해 호출될 함수이다.
라는 사실을 알려주는 것이다. - 이를 사용하면 다음과 같은 이점을 가진다.
- 클래스의 인터페이스를 이해하기 좋게 한다. 해당 클래스로 만들어진 객체를 변경할 수 있는 함수는 무엇이고, 그렇지 않은 함수는 무엇인가를 사용자측에서 알게 해준다.
const
키워드를 통해 상수 객체를 사용할 수 있게 할 수 있다.
상수 객체에 대한 참조자(reference to const)를 함수의 매개 변수로 전달하는 것은 성능에 있어서 중요한 부분을 차지한다. 이 기법을 제대로 사용하려면 상수 상태로 전달된 객체를 조작할 수 있는
const
멤버 함수가 필요하다.
const
키워드가 있고 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다.- 실제 프로그래밍에서 상수 객체가 생기는 경우는 다음과 같다.
- 상수 객체에 대한 포인터로 객체가 전달
- 상수 객체에 대한 레퍼런스로 객체가 전달
class TextBlock {
public:
TextBlock(const std::string& _text) : text{ _text } {};
static const int MaxSize = 10;
enum { EnumMaxSize = 10 };
char string[MaxSize];
const char& operator[](std::size_t position) const {
std::cout << "상수 멤버 함수 호출됨\n";
return text[position];
}
char& operator[](std::size_t position) {
std::cout << "일반 멤버 함수 호출됨\n";
return text[position];
}
private:
std::string text;
};
// 함수의 매개 변수로 상수 객체의 참조자가 전달됨
void print(const TextBlock& ctb) {
std::cout << ctb[0] << std::endl;
}
int main() {
// 일반 멤버 함수 호출
TextBlock tb("Hello");
std::cout << tb[0] << std::endl;
// 상수 멤버 함수 호출
const TextBlock constTb("Hello");
std::cout << constTb[0] << std::endl;
// 상수 멤버 함수 호출
print(tb);
// 비상수 객체(char&)에 대한 쓰기 가능
tb[0] = 'x';
// 상수 객체(const char&) 대한 대입 연산 금지로 에러 발생
// constTb[0] = 'x';
}
- 만약, 위의 코드에서 비상수 함수의 리턴 타입이
char
라면 레퍼런스가 아니기 때문에 대입 연산을 사용할 수가 없게 된다.
// if
// ...
char operator[](std::size_t position) {
return text[position];
}
// ...
TextBlock tb("Hello");
// 에러 발생. tb[0]의 사본에 값 대입됨
// tb[0] = 'x';
- 클래스의 멤버 함수가 상수 멤버(const)라는 것은 다음과 같은 두 의미를 가진다.
비트수준 상수성(물리적 상수성)
- 비트수준 상수성은 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가
const
라고 인정하는 개념이다.(static 멤버는 제외) - 즉, 그 객체를 구성하는 비트들 중 어떤 것도 변경하면 안된다는 것이다.
- 객체의 멤버에 대해 대입 연산 수행만 확인하면 비트수준 상수성을 컴파일러 단계에서 확인할 수 있다.
- C++에서 정의하고 있는 상수성은 비트수준 상수성이다.
- 따라서 상수 멤버 함수는 그 함수가 호출된 객체의 어떤 비정적 멤버도 수정할 수 없다.
논리적 상수성
- 그런데 어떤 포인터가 가리키는 대상을 수정하는 멤버 함수들은 진짜 const로 동작하지 않는데, 비트수준 상수성 검사(대입 연산 확인)를 통과한다.
- 해당 포인터가 객체의 멤버로 들어 있는 한 비트수준 상수성을 가지는 것으로 판별 된다.
- 논리적 상수성은 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자측에서 알아채지 못하면 상수 멤버 자격이 있다는 것이다.
상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법
- 상수 멤버화 비상수 멤버 함수가
const
키워드가 추가된 것을 제외하고 정확하게 같은 일을 한다면 캐스팅을 사용하여 코드 중복을 피할 수 있다.- 간단하게 말하면 비상수 멤버 함수에서 캐스팅을 통해 상수 버전을 호출하는 것이다.
- 상수 멤버 함수에서 비상수 멤버 함수를 호출하는 것은 상수 멤버 함수의 비트적 상수성을 깨는 것이기 때문에 좋지 않다.
- 상수 멤버에서 비트적 상수성을 깨는 것은 안전성에 문제가 있기 때문에 재앙의 씨앗이 될 수 있으므로 권장하지 않는다.
class TextBlock {
public:
TextBlock(const std::string& _text) : text{ _text } {};
static const int MaxSize = 10;
enum { EnumMaxSize = 10 };
char string[MaxSize];
const char& operator[](std::size_t position) const {
std::cout << "상수 멤버 함수 호출됨\n";
return text[position];
}
// 1. 상수 멤버 함수를 호출하기 위해 this를 const TextBlock&으로 캐스팅
// - 비상수 객체에서 상수 객체로 변경은 안전한 캐스팅이기 때문에 static_cast 사용
// 2. 호출된 상수 멤버 함수의 결과값에서 const를 떼어버리기 위해 char&으로 캐스팅
// - 상수 객체를 비상수 객체로 변환하는 방법은 const_cast 말고 방법이 없음
char& operator[](std::size_t position) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
private:
std::string text;
};
메모
- const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 준다. const는 어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환 타입에도 붙을 수 있으며, 멤버 함수에도 붙을 수 있다.
- 컴파일러 쪽에서 보면 비트수준 상수성을 지켜야 하지만, 프로그래머는 논리적인 상수성을 사용해서 프로그래밍해야 한다.
- 상수 멤버 및 비상수 멤버 함수가 기능적으로 서로 똑같다면 코드 중복을 피하는 것이 좋은데, 이때 비상수 버전이 상수 버전을 호출하도록 만들어야 한다.
https://github.com/Jeongmin94/ECPP/tree/main/01_Accustoming_Yourself_To_Cpp
'책 > 이펙티브 C++' 카테고리의 다른 글
이펙티브 C++(80p ~ 89p) (1) | 2024.01.11 |
---|---|
이펙티브 C++(71p ~ 79p) (1) | 2024.01.10 |
이펙티브 C++(51p ~ 60p) (1) | 2024.01.08 |
이펙티브 C++(35p ~ 50p) (0) | 2024.01.04 |
이펙티브 C++(0p ~ 34p) (1) | 2024.01.03 |