일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Effective c++
- 반복자
- implicit conversion
- 영화
- more effective c++
- 참조자
- 루아
- 언리얼
- reference
- 게임
- 티스토리챌린지
- UE4
- 영화 리뷰
- 예외
- 비교 함수 객체
- 메타테이블
- 상속
- effective stl
- 스마트 포인터
- Vector
- operator new
- 암시적 변환
- exception
- resource management class
- Smart Pointer
- 다형성
- 오블완
- lua
- c++
- virtual function
Archives
- Today
- Total
스토리텔링 개발자
[Effective STL] 22. set 요소의 key 바꾸기 금지 본문
728x90
항목 22. set과 multiset에 저장된 데이터 요소에 대해 키(key)를 바꾸는 일은 피하자
이유
- set과 multiset은 요소를 정렬해서 관리하며, 정렬됨을 간주하고 동작한다.
- 임의로 값을 바꾸면 그 정렬이 올바를 리가 없기 때문이므로 임의로 키값을 바꾸면 안 된다.
- 어차피 모던 c++에서는 set은 cbegin만 지원하므로 const가 아닌 요소에 접근하기 쉽지 않다.
map에서는?
map<int, string> m;
...
m.begin()->first = 10; // 컴파일 에러
multimap<int, string> mm;
...
mm.begin()->first = 20; // 역시 컴파일 에러
- map, multimap은 애초에 pair<const K, V> 값을 저장하기 때문에 변경할 수 없다.
- 물론 const_cast를 써서 억지로 바꿀 수는 있겠지만..
- 어쨌건 set은 const가 붙어있지 않으므로 마음만 먹으면 맘대로 바꿀 수 있다.
set의 요소가 const가 아닌 이유
// 요소
class Employee
{
public:
...
const string& name() const;
void setName(const string& name);
const string& title() const;
void setTile(const string& title);
int idNumber() const;
};
// 비교 함수 객체
struct IDNumberLess
{
bool operator()(const Employee& lhs, const Employee& rhs) const
{
return lhs.idNumber() < rhs.idNumber();
}
};
using EmplDSet = set<Employee, IDNumberLess>;
EmplDSet se; // 식별 번호로 정렬되는 직원 데이터의 set
Employee selectedID;
...
EmplDSet::iterator i = se.find(selectedID);
if(i != se.end())
{
i->setTitle("Corporate Deity"); // 키 값을 바꾼 것은 아니므로 문제 없음
}
- 즉, 이런 식으로 키가 아닌 값이 바뀌어야 하는 상황이 생기므로 const가 아닌 것이다.
- map / multimap도 키값에 이런 방식을 사용할 수 있는 거 아닌가?
- 하지만 표준화 위원회는 map은 키 값을 const로, set은 키 값을 const가 아닌 것으로 정하였다.
- 참고) 지금은 반대로 set 역시 키 값이 const인 것을 사용하도록(cbegin) 사양이 변경되었다.(C++11)
- 컴파일러가 set에서 const가 붙은 키값을 제공하는 경우도 있는데 key 값을 막 바꿔도 될까?
- 이식성에 별 생각이 없다면 키 값 외의 부분을 그냥 바꿔버리자.
- 이식성을 고려한다면 set 요소를 건들지 말자.
- 이식성을 고려해서 const_cast를 사용한다면?
- 참조자(reference) 캐스팅을 사용하여 처리하자.
EmplDSet::iterator i = se.find(selectedID);
if(i != se.end())
{
const_cast<Employee&>(*i).setTitle("Corporate Deity");
}
- 캐스팅 실패 사례
EmplDSet::iterator i = se.find(selectedID);
if(i != se.end())
{
static_cast<Employee>(*i).setTitle("Corporate Deity"); // 실패 1
((Employee)(*i)).setTitle("Corporate Deity"); // 실패 2
}
- 위 두 방법은 똑같은 기계어 코드를 만들고, 동일한 이유로 오동작한다.
- 수행한 결과는 *i의 사본인 임시(temporary) 객체이며, 여기에 대해 setTile을 호출한다.
- map, multimap의 경우 key가 const로 못박혀 있으므로 const_cast로 상수성을 날리는 것에 대한 고려는 되어있지 않을 수 있다.
- 이론적으로 읽기 전용의 메모리 위치에 기록하도록 구현될 수도 있으므로 상수성을 날리는 건 위험할 수 있다.
연관 컨테이너 값을 안전하게 바꾸기
- 변경하고자 하는 컨테이너 요소의 위치를 탐색한다.(항목 45 참조)
- 탐색한 요소의 복사본을 만든다.
- 컨테이너에서 그 요소를 없앤다. 대개 erase를 호출한다.(항목 9 참조)
- 만들어둔 복사본의 정보를 바꾼다.
- 복사본을 컨테이너에 새로 삽입한다.
- 삽입할 위치가 제거했던 위치와 같거나 그 옆이면 insert에 단계 1에서 얻은 반복자를 넘기면
- 삽입 시간을 로그 시간에서 상수 시간으로 단축시킬 수 있다.
EmplDSet se;
Employee selectedID;
...
EmplDSet::iterator i = se.find(selectedID); // 1. 요소 탐색
if(i != se.end())
{
Employee e(*i); // 2. 복사
se.erase(i++); // 3. 삭제, 그리고 반복자 유효성을 유지하기 위해 후위 증가
e.setTitle("Corporate Deity"); // 4. 복사본 수정
se.insert(i, e); // 5. 재삽입
}
결론
- 연관 컨테이너는 요소 정보를 바꿀 때에는 바꾼 후에도 반드시 모든 요소가 정렬되어 있도록 유지해야 한다.
728x90
'개발 > Effective STL' 카테고리의 다른 글
[Effective STL] 24. map::operator[] vs map::insert (0) | 2024.12.09 |
---|---|
[Effective STL] 23. 이진 탐색 트리 vs 정렬된 vector (0) | 2024.12.06 |
[Effective STL] 21. 연관 컨테이너 비교 함수 객체의 동일값 비교 (0) | 2024.12.04 |
[Effective STL] 20. 컨테이너 비교 함수 객체 (0) | 2024.12.03 |
[Effective STL] 19. 상등(equality)과 동등(equivalence) 관계 (0) | 2024.11.29 |
Comments