const char * p; or char const * p;
const 키워드가 *의 왼쪽에 있으면 포인터가 가르키는 대상이 상수
char * const p;
const 키궈드가 *의 오른쪽에 있으면 포이터 자체가 상수
STL반복자에 const를 쓸 경우
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); // T* const 와 같은 동작을 합니다.
*iter = 10 ; // OK! iter가 가리키는 대상을 변경합니다.
++iter; // Error! iter는 상수입니다.
std::vector<int>::const_iterator cIter = vec.begin();// const T*와 같은 동작을 합니다.
*cIter = 10; // Error! cIter가 상수이기 때문에 안됩니다.
++cIter; // OK!
함수의 반환값을 상수로 지정하면 이점이 있습니다.
안전성이나 효율을 포기하지 않고도 사용자측의 에러 돌발 상황을 줄이는 효과를 얻을 수 있습니다.
const Rational operator* ( const Rational& lhs, const Rational& rhs );
Rational a, b, c;
(a * b) = c; //Error! 우리가 원하는 코드는 ( a * b) == c, 분명한 실수!
우리는 const 키워드를 쓰면, 이같은 실수를 미연에 방지 할 수 있습니다.
즉!
매개변수 혹은 지역 객체를 수정할 수 없게 하는 것이 목적이라면 const로 선언하는 것을 잊지 맙시다.
상수멤버함수
멤버 함수에 붙는 const 키워드의 역할은?
"해당 멤버 함수가 상수 객체에 대해 호출될 함수 이다"라는 사실을 알려주는 것입니다.
상수멤버함수의 중요성
1. 클래스의 인터페이스를 이해하기 좋게 하기위해서
- 그 클래스로 만들어진 객체를 변결할 수 잇는 함수는 무엇이고, 변경할 수 없는 함수는 무엇인가?
2. 이 키워드를 통해 상수객체를 사용할 수 있게 하자는 뜻
C++
프로그램의 실행 성능을 높이는 핵심 기법 중 하나가 객체 전달을 상수 객체에 대한 참조자를 사용하는 것이다. 이 기법이 제대로
살아 움직이려면 상수 상태로 전달된 객체를 조작할 수 있는 const 멤버 함수, 즉 상수 멤버 함수가 준비되어 있어야 한다는 것
"const 키워드가 함수에 있고, 없고의 차이만 있는 멤버 함수들은 오버로딩이 가능하다"
1번. const char& operator[](std::size_t position) const ;
2번. char& operator[](std::size_t position);
const TextBlock ctb("World");
std::cout<<ctb[0]; //1번 호출
ctb[0]
= 'X'; // Error! 리턴타입이 const char&이기 때문인데, 이 이유뿐만아니라, 상수 객체로 선언하였기
때문에, 말그대로 데이터를 변경한다는것은 말이 안된다. 그래서 리턴타입도 const 를 붙여줍니다.
TextBlock tb("Hello");
std::cout<<tb[0]; // 2번 호출
tb[0] = 'X'; // OK!
여기서
중요한점 또한가지는 리턴형이 char& 라는것인데, 말 그대로 우리가 변경할 값을 변경하기 위해서는 &로 리턴을
해야합니다. &로 리턴하지 않으면, 그냥 리턴되는 복사본에다가 우리는 대입하는것이기때문에 실질적으로 변경하려는 값에는
변경이 안되는 것이죠.
어떤 멤버함수가 상수 멤버라는 것이 대체 어떤 의미를 지닐까요?
1. 비트수준 상수성 ( 물리적 상수성 )
- 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 const 임을 인정
ex ) 비트 수준 상수성 검사를 통과하는 멤버함수
class CTextBlock {
public:
char& operator[] (std::size_t position) const // 내부 데이터에 대한 참조자를 반환
{ return pText[position]; }
private:
char *pText;
};
위의 코드에서 operator[] 는 pText를 건드리지는 않습니다. 컴파일 오류도 없습니다.
이로써 컴파일러는 내부의 데이터를 건드리지 않았기 때문에 비트수준 상수성에서 통과 해버립니다.
사실상 위의 operator[]는 비트수준 상수성에 위배되는 것입니다.
아무 문제가 없어 보이지만, 이코드에는 커다란 문제점이 있습니다.
const CTextBlock cctb("Hello");// 상수객체 선언
char *pC = &cctb[0]; // operator[]를 호출하여 cctb의 내부 데이터에 대한 포인터
// 를 얻습니다.
*pC = 'J'; // 값을 J로 변경할 수 있습니다.
이처럼 반환된 포인터나 참조자에 의해서 외부에서 값이 변경될 우려가 있는것 입니다.
물론 함수 앞에 const 키워드를 붙여주면, 외부에서 값이 변경될 우려는 사라지게 됩니다.
코드를 보자는게 아니라, 가장 중요한 부분은 pText[position]; 멤버를 건드렸다는 사실입니다. 이것은 즉
비트수준 상수성을 위배한것입니다.
그래서
일부 몇비트정도만 상수멤버에서 바꿀수 있게 하는 개념인 논리적 상수성 이라는 개념이 나온 것입니다.
2. 논리적 상수성
- 상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다.
class CTest {
private:
char * pText;
std::size_t num; // mutable std::size_t num; 선언시 상수함수에서 접근 가능
public:
std::size_t plus() const {
num = 10; //Error! 상수 멤버 함수다. 데이터를 변경할 수 없다.
}
};
위의 코드는 논리적 상수성에서 말하는 개념을 그대로 반영한 것이다. 몇 비트만 바꾼것이다.
하지만 위의 코드는 컴파일 에러가 납니다. 당연합니다. 또한 비트수준상수성과는 거리가 멉니다.
컴파일 에러를 수정하기 위해선 mutable 이라는 키워드를 변수앞에 붙여 주면 해결됩니다.
mutable std::size num; // OK!
즉!
컴파일러는 비트수준상수성을 지키고,
우리는 논리적 상수성으로 일부 비트만 수정할 수 있게 그때 마다 프로그래밍을 해야합니다. 완벽하게 비트수준상수성을 지킨다면
좋겠지만, 프로그램을 하다보면 그렇지 않은 경우가 많습니다. ( mutable을 난무하게되면, 아무의미 없음을 강조합니다 )
상수 멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법
같은 수행을 하는 함수지만, const 상수여부에따라 오버로딩된 함수일경우 똑같은 코드가 중복되게 됩니다. 이중복을 피하기 위해서는 비상수버전 에서 상수버전 함수를 호출하도록 만들어야합니다.
ex ) 비상수 버전에서 상수버전 함수를 호출하는 예
class Test{
const char& operator[] ( std::size_t position ) const
{
...
...
return text[position];
}
char& operator[] ( std::size_t position )
{
/**
* const_cast는 const를 띠는 역할을 하고, static_cast는 const를 붙이는 역할을 한다.
*/
return const_cast< char& >( static_cast< const Test& >(*this)[position] );
}
}
위의 코드를 해석하자면,
먼저 this포인터를 const를 붙여서 []연산자를 이용해 상수함수를 호출하고, 상수함수의 리턴값을 받고 리터값에 달리 const를 띠어내면서 값을 참조 리턴하고 있다.
위의 코드 처럼 하게되면, 안정성도 유지하면서 코드중복도 피할 수 있습니다.
이것 만은 잊지 말자!
◆ const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 줍니다. const는 어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환 타입에도 붙을수 있으며, 멤버함수에도 붙을 수 있습니다.
◆ 컴파일 쪽에서 보며녀 비트수준 상수성을 지켜야 하지만, 여러분은 개념적인(논리적인)상수성을 사용해서 프로그래밍 해야 합니다.
이것 만은 잊지 말자!
◆ 상수 멤버 및 비상수 멤버 함수가 기능적으로 서로 똑같게 구현되어 있을 경우에는 코드 중복을 피하는 것이 좋은데, 이때 비상수 버전이 상수 버전을 호출하도록 만드세요