포인터와 const
const를 이용하면 Pointer 자체를 상수로 만들거나 Pointer가 가리키는 데이터를 상수로 만들거나 둘 다 적용을 시킬수 있다.
char greeting[] = "hello";
char* p = greeting; //비상수 포인터, 비상수 데이터
const char* p = greeting //비상수 포인터, 상수 데이터
char* const p = greeting //상수 포인터, 비상수 데이터
const char* const p =greeting // 상수 포인터, 상수 데이터
void f1(const Widget* pw) == void f1(Widget const* pw) // const위치가 무얼 기준하는지 암기!
const 기준
const가 * 앞에 있으면 데이터를 상수화
const가 * 뒤에 있으면 포인터를 상수화
STL과 const
STL Iterator도 포인터를 본떠서 기본적인 동작 원리가 T* 포인터와 진짜 흡사하다.
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*iter = 10; // OK!
++iter; // ERROR! iter는 상수!
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10; // ERROR! *cIter는 상수!
++cIter; // OK!
함수 선언과 const
const의 강력한 용도는 함수 선언에서 사용할 때이다. 함수 반환 값을 상수로 정해 주면, 안전성이나 효율을 포기하지 않고도 사용자측의 에러 돌발 상황을 줄이는 효과를 꽤 볼 수 있다.
const Rational operator* (const Rational& lhs, const Rational& rhs);
여기서 함수 반환 값이 const인 이유는 다음의 경우를 예방하기 위해서이다.
Rational a,b,c;
(a*b) = c;
상수 객체를 반환하지 않는다면 위의 어처구니 없는 연산식이 허용이 된다.
뿐만아니라, 다음과 같은 사용자의 실수도 허용하게 된다.
if(a*b =c) ...
상수 멤버 함수와 const
멤버 함수에 붙은 const 키워드는 "해당 멤버 함수가 상수 객체에 대해 호출될 함수이다"라는 사실을 알려 주는 것이다.
이런 함수의 장점은 무엇일까?
(1) 클래스의 인터페이스를 이해하기 좋다
그 클래스로 만들어진 객체를 변경할 수 있는 함수는 무엇이고, 또 변경할 수 없는 함수는 무엇인가를 사용자쪽에서 알수 있다.
(2) 코드의 효율을 위한 방법으로 상수 객체를 사용할 수있다
const가 있는 버젼과 없는 버젼으로 함수 오버로딩이 가능하다
class TextBlock{
public:
...
const char& operator[](std::size_t position)const
{return text[position];}
char& operator[](std::size_t position)
{return text[position];}
private:
std::string text;
};
사용처는 이렇다.
TextBlock tb("Hello");
std::cout<< tb[0]; // 비상수 멤버 함수 호출
const TextBlock ctb("World");
std::cout<< ctb[0]; // 상수 멤버 함수 호출
// 비상수 멤버 함수 호출
void print(const TextBlock& ctb){
std::cout<< ctb[0];
...
}
const의 의미, 상수성
1. 비트수준 상수성
- 다른 말로 물리적 상수성이라 불린다. 비트수준 상수성은 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버 함수가 'const'임을 인정하는 개념이다. C++에서 정의하고 있는 상수성이 비트수준 상수성이다.
- 어떤 포인터가 가리키는 대상을 수정하는 멤버함수에 대해서도 비트수준 상수성 검사를 통과한다. 즉, 상수 멤버를 호출했더니 값이 변하는 어처구니 없는 상황이 발생할 수 있다.
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'; // 값의 변경이 허용됨
2. 논리적 상수성
- 논리적 상수성은 위와 같은 황당한 상황을 보완하는 대체 개념으로 등장하였다. 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자측에서 알아채지 못하게만 하면 상수 멤버 자격이 있다는 것이다.
class CTextBlock{
public:
...
std::size_t length() const;
private:
char* pText;
std::size_t textLength;
bool lengthIsValid;
};
std::size_t CTextBlock::length() const{ //const 임을 주의하자!
if(!lengthIsValid){
textLength = std::strLen(pText); //Error !
lengthIsValid = true; //Error!
}
return textLength;
Error를 피하면서 비트 수준의 상수성을 지키기 위한 방법
class CTextBlock{
public:
...
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength; //mutable
mutable bool lengthIsValid; // mutable
};
std::size_t CTextBlock::length() const{ //const 임을 주의하자!
if(!lengthIsValid){
textLength = std::strLen(pText); //Error !
lengthIsValid = true; //Error!
}
return textLength;
const와 코드 중복
상수 객체/ 비상수 객체 함수가 그저 상수 객체/ 비상수 객체를 반환한다는 점을 제외하고 로직이 같음에도 불구하고 코드가 중복적으로 작성되어 있다면 비상수 객체 함수를 상수 객체를 이용해서 반환하도록 하여 코드 중복을 피할수 있다.
class TextBlock{
public:
...
const char& operator[](std::size_t position) const{
... // 생각보다 엄청나게 많은 코드들이 들어갈 수 있다.
return text[position];
}
char& operator[](std::size_t position){
...
...
...
return text[position];
}
private:
std::string text;
}
코드 중복을 피하는 방법
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]);
}
private:
std::string text;
}
const TextBlock 형식으로 형변환하여 [] 연산을 한 후 const_cast를 통해 const를 제거함으로써 비상수 데이터를 반환하게 된다.
이것만은 잊지말자!
1. const를 붙여 선언하면 컴파일러가 사용상의 에러를 잡아내는 데 도움을 줍니다. const는 어떤 유효범위에 있는 객체에도 붙을 수 있으며, 함수 매개변수 및 반환 타입에도 붙을 수 있으며, 멤버 함수에도 붙을 수 있습니다.
2. 컴파일러 쪽에서 보면 비트수준 상수성을 지켜야 하지만, 여러분은 개념적인 상수성을 사용해서 프로그래밍 해야 합니다.
3. 상수 멤버 및 비상수 멤버 함수가 기능적으로 서로 똑같게 구현되어 있을 경우에는 코드 중복을 피하는 것이 좋은데, 이때 비상수 버전이 상수 버전을 호출하도록 만드세요.
'C++ > Effective C++' 카테고리의 다른 글
[Effectivce C++] 6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2020.07.27 |
---|---|
[Effectivce C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2020.07.27 |
[Effectivce C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자. (0) | 2020.07.27 |
[Effective C++] 2. #define을 쓰려거든 const,enum,ilnine을 떠올리자 (0) | 2020.07.13 |
[Effective C++] 1. C++를 언어들의 연합체로 바라보는 안목은 필수 (0) | 2020.07.13 |