스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!
💡 const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는데 도움을 준다!
💡 const는 어떤 유효범위에 있는 객체라도, 함수 매개변수 및 반환 타입에도, 멤버 함수에도 붙을 수 있다!
💡 컴파일러 입장에서 보면 비트수준 상수성을 지켜야 하지만, 논리적인 상수성을 사용해서 프로그래밍해야 한다!
💡 상수 멤버 및 비상수 멤버 함수가 기능적으로 서로 똑같이 구현되어 있을 경우, 코드 중복을 피하기 위해 비상수 버전이 상수 버전을 호출하도록 만들면 된다!
의미적인 제약을 소스코드 수준에서 할 수 있다.
(((const == 외부 변경 불가능))) <- 이 제약을 컴파일러가 지켜준다.
char greeting[] = "Hello";
char* p = greeting; // 비상수 포인터, 비상수 데이터
const char* p = greeting; // 비상수 포인터, 상수 데이터
char* const p = greeting; // 상수 포인터, 비상수 데이터
const char* const p = greeting; // 상수 포인터, 상수 데이터
const * : 포인터가 가리키는 대상(데이터)이 상수
* const : 포인터 자체가 상수
포인터가 가리키는 대상이 상수일 경우, const를 Type 앞 뒤 아무데나 붙여도 상관없음!
void f1(const Widget *pw);
void f2(Widget const *pw);
// 두 개는 같은 의미
동작원리가 T* 포인터와 흡사하다.
반복자를 const로 선언 == 포인터를 상수로 선언하는 것
반복자 : 자신이 가리키는 대상이 아닌 것을 가리키기 X
but 반복자가 가리키는 대상 변경 O
변경이 불가능한 객체를 가리키는 반복자 -> const_iterator
iterator == T* const 포인터 (ptr가 const)
const_iterator == const T* 포인터 (대상이 const)
어휴,,
vector<int> vec;
const vector<int>::iterator iter = vec.begin();
*iter = 10; // OK! 가리키는 대상을 변경할 수 있다
++iter; // ERROR! (iter == 상수)
vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; // ERROR! *cIter == 상수
++cIter; // OK! cIter를 변경하는건 가능
함수 리턴값, 매개변수, 멤버 함수, 함수 전체에 const 가능
어이없는 상황에서의 불필요한 에러를 없앨 수 있다
class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational a, b, c;
...
(a * b) = c; // a * b의 결과는 const Rational인데
// operator=를 호출해버렸다
웬만하면 매개변수, 리턴값에 const를 붙이자..
멤버 변수를 변경시키지 않는 멤버함수!!
상수 객체가 호출할 수 있는 함수
중요한 이유
1. 클래스의 인터페이스를 이해하기 좋게 하기 위해
객체 전달을 상수 객체에 대한 참조자 (Call-By-Reference)로 하는 것이 당연히 좋다
(Call by Value 하면 복사생성자 호출됨,, 성능저하의 주범)
-> 상수 상태로 전달된 객체를 조작할 수 있는 상수 멤버 함수가 필요하다!
근데 두 함수의 내용은 똑같을거고,, 상수인지 아닌지에만 차이가 있다.
그냥 똑같은 내용의 함수 2개를 놓기는 좀 그렇다
사실 근데
상수 -> 상수함수 (O)
상수 -> 비상수 함수 (X) 상수 객체가 변경될 수도 있따.
비상수 -> 상수 함수 (O)
비상수 -> 비상수함수 (O)
라서.. 그냥 비상수가 상수 함수를 호출하도록 만들면 됨
class TextBlock {
public:
...
// 상수 함수
const char& operator[] (std::size_t position) const {
...
return text[position];
}
// 비상수 함수
char& operator[] (std::size_t position) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
...
비상수 operator[]에 대해서
1. static_cast<>를 통해 const로 만든다
2. 상수 버전의 함수를 호출한다
3. const_cast<>를 통해 리턴값의 const를 제거하여 리턴한다
끝!
🔥 주의!
비상수 객체 -> 상수 객체는 안전하기 때문에 static_cast를 써도 되지만,
상수 객체 -> 비상수 객체의 경우 반드시 const_cast를 써야만 한다.
== 물리적 상수성
: 멤버함수는 객체를 구성하는 비트들 중 어떤 것도 바꾸면 안된다!
컴파일러는 비트수준 상수성만을 따진다 (= 대입 연산자가 있는지만 확인한다)
-> 어떤 포인터가 가리키는 대상을 수정하는 멤버함수가 가끔 이 조건을 통과해버림
const의 역할을 하지 못하는데도,,
class CTextBlock {
public:
...
char& operator[](std::size_t position) const {
return pText[position];
}
private:
char* pText;
};
...
const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';
//-> cctb == "Jello"
그래서 논리적 상수성이 등장하게 됨!
객체의 일부 몇 비트 정도는 바꿀 수 있되, 그 것을 사용자 측에서 알아채지 못하면 const이다!
: 비정적 데이터 멤버를 비트수준 상수성의 족쇄에서 풀어준다!
mutable 키워드가 붙은 데이터 멤버들은 어떤 순간에도 수정할 수 있다!!!!
나한테는 const가 약간.. 좀 어려웠다
건들면 안되는 걸 의미하는 const가.. 말 그대로 건들면 안되는 기분?ㅋㅅㅋ
그래서 const를 진짜 내 손으로 쓰는 일이 거의 없었다
바보였지,,,
상수와 조금 더 친해진 기분이다
항목 하나씩 읽어갈 수록 조금씩 퍼즐이 끼워맞춰지는 기분?
이제서야,,