프로그래밍 언어/C/C++

[읽은 글] const correctness

소혼 2016. 10. 18. 10:13
반응형

https://herbsutter.com/2013/05/24/gotw-6a-const-correctness-part-1-3/

https://herbsutter.com/2013/05/24/gotw-6b-const-correctness-part-2/

https://herbsutter.com/2013/05/28/gotw-6b-solution-const-correctness-part-2/


* 번역이 아니라 이해하고 정리한 내용입니다. 따라서 원 글의 의도가 전달되지 않을 수 있으며, 수정 될 수 있습니다.


[ ] https://herbsutter.com/2013/05/24/gotw-6a-const-correctness-part-1-3/


1. shared variable

Shared variable이란 하나 이상의 thread에서 동시에 접근할 수 있는 변수를 말한다.


2. shared variable의 관점에서 const 나 mutable이란?

const member function은 다음 두가지 중 하나를 반드시 만족해야 한다. (절대 공유되지 않는다는 가정이 있지 않는 한)

 - 진짜로 const(truly physically/bitwise const). 다시 말해 객체의 어떤 데이터에도 write하지 않아야 한다.

 - 내부적으로 동기화되어서 (예를 들어 mutex, atomic<> 등으로) 동시에 호출되더라도 문제가 없어야 한다.


비슷하게, 멤버 변수에 mutable 키워드를 쓴다는 것은 그 변수가 쓸 쑤 있지만 논리적으로는 const(writable but logically const) 임을 의미한다.

- 논리적으로 const란 여러 동시성 (const) 연산들로부터 안전하다는 의미이다.

- mutable이 붙어있다는 것은 어떤 const operation이 사실은 write할지 모른다는 뜻이고, 동시성을 갖고 읽고 쓰는데 문제가 없다는 의미이다. 따라서 mutex 나 유사한 동기화 로직 아니면 atomic<>으로 만들어야 한다.


따라서, mutex 와 mutable 을 같이 써야 한다. (또는 atomic<> 과 mutable)


3. c++98과 c++11에서 mutable과 const는 뭐가 다른가?


먼저 그전에...

이 내용을 정리하기 전에, const와 mutable에 대해 헷갈리게 했던 부분은 const_cast를 통해 수정하면 되지 왜 mutable을 써야 하지? 하는 점이었다.

예를 들면 아래 처럼...


#include <vector>

class Tile { ... };

class TileStore {
public:
Tile* getTile() const {
if (empty_tiles_.empty()) {
const_cast<TileStore*>(this)->ReserveMoreTiles();
}
// ...
return //something;
 }
void ReserveMoreTiles() {
// ...
}
private:
std::vector<Tile*> empty_tiles_;
};


먼저 아래 링크을 가져왔다.

http://stackoverflow.com/questions/11457953/const-cast-vs-mutable-any-difference


const_cast는 사실 대상 객체의 const 속성을 제거하는 것이 아니다. 대신 접근 경로(access path)상에 const 속성을 바꿔주는 것이다.

따라서 진짜 const 객체에 대하여 const_cast를 수행하는 것은 Undefined behavior를 야기할 수 있다.


  const int j = 5; // constant object
  const int *p = &j; // `p` is a const access path to `j`

  int *q = const_cast<int *>(p); // `q` is a non-const access path to `j`
  *q = 10; // UNDEFINED BEHAVIOR !!!


그럼 mutable은 뭘까? [1]

 - mutable로 지정된 멤버는 외부에 노출된 클래스의 상태(the externally visible state of the class)에 영향을 받지 않는다.

 - const 클래스의 mutable 멤버는 수정 가능하다.

 - c++은 mutable을 storage-class-specifier로 취급한다.


mutable이 단순히 상수성을 제거한다는 생각을 했는데, 중요한 포인트중 하나는 class가 const여도 된다는 것이다.

즉,

* const_cast는 애초에 다루는 객체가 const이면 안된다.

* mutable은 다루는 객체가 const여도 modifable하다.


위에 적당히 만들어본 Tile & TileStore를 아래처럼 고쳐보았다.


#include <vector>
#include <stdio.h>

class Tile { public: int i; };
class TileStore { public: Tile* getTile() const { if (empty_tiles_.empty()) { const_cast<TileStore*>(this)->ReserveMoreTiles(); }
return empty_tiles_[0]; } void ReserveMoreTiles() {
Tile* tile = new Tile(); tile->i = 10; empty_tiles_.push_back(tile); } private: std::vector<Tile*> empty_tiles_; };
int main() { const TileStore a; Tile* t = a.getTile(); printf("%d\n", t->i); return 0; }

이 동작은 undefined behavior이다. const 객체의 constness를 제거하는 로직이니까.

mutable로 empty_tiles_를 선언해야 맞는 것 같다.


결론적으로 const_cast는 위험하다! (const 객체 안쓴다고 확신할 수 있는가?)


PS. 하지만, QT 5.6으로 컴파일뿐 아니라 실행은 잘 된다. (undefined.... != crash)


3으로 다시 돌아가서....


C++98 single threaded code는 문제 없다. ( 왜 c++98 single threaded code라고 부르는지 모르겠다. )

C++98에서 우리는 const는 논리적인 const(logically const)이며, 물리적인/비트레벨의 const(phisically/bitwise)는 아니다라고 가르쳐왔다.


이제 C++11에서는 const란 read-only 또는 동시성을 고려해서 안전하게 읽을수 있음(safe to read concurrently)을 보장한다.(해야 한다.)

다시 말해 물리적인/비트레벨의 const이거나 내부적으로 동기화되어 어떠한 쓰기 동작도 concurrent한 read access들로부터 동기화되어야 한다.


요약해서,

const correctness 코드를 짜라.

const를 올바르게 사용하는 것은 올바르게 동기화된 코드를 위해 필요하다. 또 const는 그 자체로 "값을 수정하지 않겠다는" 훌륭한 문서이고 컴파일러가 더 나은 코드를 만드는 것을 도와준다.


cosnt 객체나 동작(메소드)에서 멤버를 수정해야 한다면 mutable을 mutex 또는 atomic과 함께 써라.

모든 상용 라이브러리들이 const correct하진 않다.


[ ] https://herbsutter.com/2013/05/28/gotw-6b-solution-const-correctness-part-2/


1. 함수를 선언할때 레퍼런스가 아닌 파라미터의 const는 의미 없다.


void a(const int);
void a(int); // same

void b(const int&);
void b(int&); // different

그러나 inline 함수는 선언과 정의가 같이 있으니까 적는게 의미가 있다.


2. 이항 연산자의 overload시 rhs는 const, lhs는 const가 아닌것이 좋다.

// 잘못된 예제 코드
polygon operator+( polygon& lhs, polygon& rhs ) {
auto ret = lhs;
auto last = rhs.get_num_points();
for( auto i = 0; i < last; ++i ) // concatenate
ret.add_point( rhs.get_point(i) );
return ret;
}

lhs를 ret에 복사하고 있다.

결국 내부에서 한번의 객체 복사가 이뤄져야 하는데, 이 객체가 앞서 호출된 임시객체인 것이 유리할 수 있다.

예를 들어 x = a + b + c;

a + b를 수행하면서 생긴 ret객체를 다시 lhs로 받아서 c와 더하게 된다.


즉, 아래와 같이 하는 것이 비용이 적을 가능성이 있다.

polygon operator+(polygon lhs, const polygon& rhs) {
// ...
return lhs;
}


참조

[1] http://en.cppreference.com/w/cpp/language/cv

반응형

'프로그래밍 언어 > C/C++' 카테고리의 다른 글

[C++] 소스코드에서 컴파일러 구분하기?  (0) 2021.01.19
[C++11] std::enable_shared_from_this  (0) 2018.02.06
[C++11] std::function의 성능  (0) 2015.05.29
GCC options  (0) 2013.12.05
[C++11] Range based for loop  (4) 2013.11.13