C++의 Const 교정자 (Correctness)

‘const’ 키워드는 해당 객체가 변형될 수 없는 것을 가리킴.

객체가 const 포인터 또는 레퍼런스에 담겼을 경우, const 멤버 함수만 호출할 수 있음.

비 const 멤버 함수를 호출하려고 하면 컴파일 에러 발생.

그런데 C++에서는 const의 위치에 따라서 의미가 변할 수도 있음.

const는 개발 하면서 넣을 곳에 그때 그때 최대한 빨리 넣어주어야 한다. 

나중에 한꺼번에 const를 추가하는 것은 비용이 많이 들고, 어렵다.

 


인자 (Parameter)

인자로 무언가를 넘길 때, 세 가지 중 하나로 넘기게 됨.

  • 포인터
  • 레퍼런스
void  pass_by_pointer( std::string *sptr );
void  pass_by_reference( std::string &sref );
void  pass_by_value( std:: string str );

읽기

const는 단어를 수식해주는 형용사처럼 오른쪽에서 왼쪽으로 읽는다. (right-to-left)

1. const X *p

  • 포인터 p는 ‘변경 불가능 객체 X‘를 가리킨다.
  • p가 가리키는 대상은 변경될 수 있음. 그러나 p가 가리키는 X는 p를 통해 변경될 수 없음.
  • X const *p 로 쓸 수도 있음.

2. X *const p

  • 상수 포인터 p‘는 객체 X를 가리킨다.
  • p가 가리키는 대상을 변경할 수 없음. 그러나 p가 가리키는 X는 p를 통해 변경될 수 있음.

3. const X *const p

  • 상수 포인터 p‘는 ‘변경 불가능 객체 X‘를 가리킨다.
  • p가 가리키는 대상을 변경할 수 없으며, p가 가리키는 X도 p를 통해 변경될 수 없음.

4. const X &x

  • 레퍼런스 x는 ‘변경 불가능 객체 X‘를 참조한다.
  • p가 참조하는 대상은 변경될 수 있음. 그러나 p가 참조하는 X는 p를 통해 변경될 수 없음.
  • X const &p 로 쓸 수도 있음.

5. X &const x

  • 상수 레퍼런스 x‘는 객체 X를 참조한다.
  • p가 참조하는 대상을 변경할 수 없음. 그러나 p가 참조하는 X는 p를 통해 변경될 수 있음.

6. const X &const &p

  • 상수 레퍼런스 x‘는 ‘변경 불가능 객체 X‘를 가리킨다.
  • p가 참조하는 대상을 변경할 수 없으며, p가 가리키는 X도 p를 통해 변경될 수 없음.

일관된 const 또는 동 const (consistent const, East const)

X const& p, X const* p 처럼 우측에 const를 붙이는 방식을 일관된 const 또는 동쪽 const 라고 부름.

이러한 스타일에서는 const인 지역 변수를 오른쪽에 씀.

int const a = 42;

식으로.

 

static일 경우에도 이렇게 씀.

static double const x = 3.14;

 

 

이러한 스타일을 직접적으로 사용해보면 아래와 같은 비교를 할 수 있음.

// 원래 스타일
const X **foo; // '변경 불가능 객체 X의 포인터'의 포인터 foo
const X *const *bar; // '변경 불가능 객체X 포인터'의 상수 포인터 bar
const X *const *const baz; // '변경 불가능 객체 X의 상수 포인터'의 상수 포인터 baz

// East const 스타일
X const **foo; // '변경 불가능 객체 X의 포인터'의 포인터 foo
X const *const *bar; // '변경 불가능 객체X 포인터'의 상수 포인터 bar
X const *const *const baz; // 변경 불가능 객체 X의 상수 포인터'의 상수 포인터 baz

 

가독성에 있어서 훨씬 이득이고, 명확한 const의 역할이 나타날 수 있지만, 아직 잘 알려지지 않아서 원래 스타일이 사용된 레거시 코드가 많다고 함.

 


const 멤버 함수

class World {
public:
  void inspect() const;
  void mutate();
};

위와 같이 const 멤버 함수는 함수 프로토타입 뒤에 const가 붙는다.

비 const 멤버 함수는 위 함수 mutate 처럼 아무 것도 붙지 않는다.

const 멤버 함수는 inspector라고도 부르며, 변경 불가능, 변경 가능 객체 모두 호출될 수 있다.

비 const 멤버 함수는 mutator라고도 부르며, 변경 가능 객체 에서만 호출될 수 있다.

 


const 멤버 함수의 레퍼런스 반환

class Person {
public:
  const std::string &name_good() const;
  std::string &name_wrong() const;
  int age() const;
  // ...
};

void misused(const Person &p) {
  p.name_wrong() = "New Name";
}

위와 같은 실수를 많이 한다고 한다. (나도 분명 했을 실수이다…)

클래스 또는 구조체 멤버 변수의 레퍼런스를 const 멤버 함수로 반환할 때는 위와 같이 반환형에도 const를 붙여야 한다.

그렇지 않으면 misused 함수의 내용 처럼, const 변수가 클래스 멤버 변수를 변경할 수도 있는 위험성이 발생한다.

값 형식 반환은?

위 코드에서 age 함수는 상관이 없는데, 어차피 int는 값 형식이기 때문에 저렇게 반환해도 멤버 변수에는 영향이 없기 때문이다.

마찬가지로 name도 레퍼런스가 아닌 std::string으로 그냥 반환했다면 const를 굳이 붙일 필요는 없을 것이다.

역시 마찬가지로 age도 레퍼런스로 반환한다면 반드시 const를 붙여주어야 할 것이다.


Foo** -> const Foo** 로 형변환

절대 이렇게 하면 안된다고 한다.

const Foo** 는 해석하면, ‘상수 객체 Foo를 가리키는 포인터’의 포인터로 해석되기 때문이라고 하며,

C++에서는 Foo* -> Foo const*로 변경은 안전하게 허용한다고 한다.

이에 의해서 Foo**는 Foo* const const* (또는 const Foo* const*)로 변경하면 된다고 한다.

class Foo { /* ... */ };

void err(const Foo** p);
void good(const Foo* const* p);

int main()
{
  Foo** p = /* ... */;
  // ...
  err(p);  // 오류!!
  good(p); // 정상!!
  // ...
}

Reference: https://isocpp.org/wiki/faq/const-correctness#const-ptr-vs-ptr-const

'삽질 정보 > C, C++' 카테고리의 다른 글

C++ Orthodox Canonical Class Form (OCCF)  (0) 2021.12.11
C++의 다형성  (0) 2021.12.11

댓글

Designed by JB FACTORY