Post

[ STUDY/Effective C++ ] 2022. 6. 18. 18:24

const의 장점

- 클래스 바깥에서 전역 혹은 네임스페이스 유표범위의 상수를 선언하는데 쓸 수 있다.

- 파일, 함수, 블록 유효범위에서 static으로 선언한 객체에도 const를 붙일 수 있다.

- 클래스 내부에서 정적 멤버 및 미정적 데이터 멤버 모두를 상수로 선언할 수 있다.

- 포인터 자체를 상수로, 포인터가 가리키는 데이터를 상수로 지정할 수 있다.

 

 

상수 포인터

char greeting[] = "Hello";
char *p = greeting; //비상수 포인터, 비상수 데이터

const char *p = greeting;// 비상수 포인터, 상수 데이터

char * const p = greeting; //상수 포인터, 비상수 데이터

const char* const p = greeting; //상수 포인터, 상수 데이터

const가 *의 왼쪽에 있으면 포인터가 가리키는 대상이 상수,

const가 *의 오른쪽에 있으면 포인터 자체가 상수

 

void f1(const Widget *pw);

void f2(Widget const *pw)

두 가지 형태 모두 함수가 받아들이는 매개변수 타입(상수 Widget 객체에 대한 포인터)이 똑같다.

 

 

반복자

const std::vector<int>::iterator iter = vec.begin();
*iter = 10;
++iter; //에러!

////////////////////////////////////////////////////
const std::vector<int>::const_iterator iter = vec.begin();
*iter = 10; //에러!
++iter;

반복자를 const로 선언하는것은 포인터를 상수로 선언하는것과 같다. 

반복자는 자신이 가리키는 대상이 아닌것을 가리키는 경우가 허용되지 않지만, 반복자가 가리키는 대상 자체는 변경이 가능하다. 만약 불가능한 객체를 가리키는 반복자가 필요하다면 const_iterator를 쓰면 된다.

 

그냥 iterator은 가리키는 대상의 변경이 가능하지만, const_iterator은 변경이 불가능하다.

 iterator은 상수이기때문에 대상의 값을 변경하는게 불가능하지만, const_iterator은 변경이 가능하다.

 

 

함수선언

함수 반환 값, 매개변수, 멤버 함수 앞, 함수 전체 에 대해서 const의 성질을 붙일 수 있다.

함수 반환값을 상수로 정해 주면, 안전성과 효율을 가지고가며 에러 돌발상황을 줄일 수 있다.

 

 

상수 멤버 함수

멤버 함수에 붙는 const키워드의 역할 : 해당 멤버 함수가 상수 객체에 대해 호출될 함수란것을 알려줌

 

이 역할의 중요성

1. 클래스의 인터페이스를 이해하기 좋게 하기위해서

그 클래스로 만들어진 객체를 변경할 수 있는 함수는 무엇이고, 또 변경할 수 없는 함수는 무엇인가를 사용자가 알아야함

 

2. 이 키워드를 통해 상수 객체를 사용할 수 있게 함

상수 상태로 전돨된 객체를 조작할 수 있는 const 멤버 함수(상수 멤버 함수) 가 준비되어야 c++ 프로그램의 실행 성능을 높이는 객체 전달을 상수객체에 대한 참조자로 진행할 수 있다.

 

 

오버로딩

const 키워드의 유무로 멤버함수들의 오버로딩이 가능하다.

 

 

비트수준 상수성(물리적 상수성)과 논리적 상수성

비트수준 상수성은 어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 그 멤버함수가 'const'임을 인정하는 개념이다.

C++에서 정의하고 있는 상수성이 비트수준 상수성이다.그래서 상수 멤버 함수는 그 함수가 호출된 객체의 어떤 비정적 멤버도 수정할 수 없게되어있다.

class CTextBlock{
public:
	...
    char& operatorr[] (std::size_t position) const
    { return pText[position];}
    
private:
	char *pText;
};



const CTextBlock cctb("Hello");
char *pc = &cctb[0];
*pc = 'J'; //cctb는 Jello가 된다.

코드를 보면 알다시피 operator함수가 비트수준 상수성이 있음으로 인해 상수 멤버 함수로 선언되어있다.

(원래라면 틀린거지만, 코드 내에서 pText를 건드리지 않기때문에 허용)

 

하지만, 어떤 값으로 초기화된 상수 객체를 하나 만들어 놓고 여기다 상수 멤버 함수를 호출하였더니 값이 변했다!

(pText를 건들지않아서 컴파일러가 넘어가줬는데!)

 

이를 보완하는것이 바로 논리적 상수성이다.

상수 멤버 함수라고해서 객체의 한 비트도 수정이 불가능한게 아니라, 몇 비트정도는 바꿀 수 있게하는것이다. 

class CTextBlock {
public:
	...
    std::size_t length() const;
    
private:
	char *pText;
    std::size_t textlength;
    bool lenthIsValid;
};

std::size_t CTextBlock::length() const
{
	if( !lengthIsValid) {
    	textLength = std::strlen(pText);
        lengthIsvalid = true;
}

 return textLength;
 }

위 코드에서 상수 멤버 함수 안에서는 textLength, lengthIsValid에 대입을 나타내는 코드인 만큼 비트수준 상수성과는 멀리 떨어져있다. 이 경우 컴파일러에서 오류를 띄우게된다.

 

    mutable std::size_t textlength;
    mutable bool lenthIsValid;

이때, mutable을 쓰게된다면 비정적 데이터 멤버를 비트수준 상수성에서 벗어나게해준다.

 

 

상수멤버 및 비상수 멤버 함수에서 코드 중복 현상을 피하는 방법

똑같은 역할을 하는 함수가 const의 유무에따라서 한번 더 쓰여 코드 중복이 된다면 컴파일시간, 유지보수, 코드 크기 등의 신경써야할것들이 늘어나게된다. 물론 보기에도 안좋고!

 

class TextBlock {
public:
	...
    const char& operator[] (std::size_t position) const
    {
    ...
    ...
    ...
    return text[position];
    }
    
    char& operator[] (std::size_t position)
    {
     return
     	const_cast<char&>( //캐스팅 적용, const떼어냄
        
         static_cast<const TextBlock&> //*this의 타입에 const붙임,op[]상수 버전 호출
          (*this) [position] 
       );
    }
    ...
};

해결법으로 캐스팅을 쓰며 안성정도 유지하며 코드중복을 피하는 방법은 비상수 operator[]가 상수 버전을 호출하도록 구현하는것이다. 총 두번의 캐스팅을 썻는데, 첫번째 캐스팅은 *this에 const를 붙이기 위함(static_cast)이고 두번째 캐스팅은 상수operator[]의 반환 값에서 const를 떼어내는 캐스팅(const_cast)이다. 

 

 

 

 

 

 

 

 

▲ top