[effective STL] 항목 22 : set과 multiset의 키를 바꾸지 말자.

2016. 2. 7. 07:12프로그래밍/Effective STL

728x90
728x90

map과 multimap에 대한 직접적인 키 변경은 불가능하다.(캐스팅만 하지 않으면)

set과 multiset은 그것이 가능하다.


set과 multiset 내의 데이터 요소가 const가 아닌 이유

일반적으로 표준화 위원회의 의도이다.

map 종류는 key만 const면 되고 set은 값이 const가 아니어야 된다는 것.


한 마디로 set은 값 자체 또는 객체 내부의 값이 key 값이 된다.

나머지 자료는 언제든 변환이 가능해야 된다. map은 key가 이미 있으므로 value 값의 경우 뭐든 상관 없다.


하지만

내 환경에선 set으로 설정하면 STL 차원에서 바로 에러를 리턴해줬다.

책에서도 어떤 STL에서는 이 코드를 거부한다더니 내가 사용하는 STL이 이 어떤 STL이었다.


1
2
3
4
5
// Error
// it->SetTitle("Power Boss");
// error C2662: 'Employee::SetTitle' : 
// cannot convert 'this' pointer from 'const Employee' to 'Employee &'
// Conversion loses qualifiers
cs


반복자 *it가 const이므로 변경할 수 없다는 말이다.


결국 되는 STL도 있고 안 되는 STL도 있다는 뜻.


핵심

여튼 저렇게 되어있지만 어떻게든 Set 내부 객체를 바꿀 수 있는 방법이 있다.

결국 중요한 것은 Set내부의 key가 되는 값을 함부로 변경하면 안 된다는 의미이다.

Set 정렬 자체가 해당 값으로 되기 때문에 컨테이너 자체가 무너지게 된다.


일단 그건 알겠고 그럼 Set의 자료들은 어떻게 바꿀 수 있는가?

방법 1 : cosnt_cast를 사용해 상수성을 없애고 값을 변경한다.

방법 2 : 복사생성자와 erase 후 insert를 해주는 방법.


예시

간단한 Employee 클래스를 설계하고 id를 이용해 Set 컨테이너에 넣고 직원을 관리한다.


https://github.com/ElementalKiss/Cpp/tree/master/Example/SetAndMultisetDoNotChangeKeyValue


Employee 클래스

header

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#pragma once
#include <set>
#include <string>
#include <functional>
#include <iostream>
 
using namespace std;
 
class Employee
{
public:
    Employee(void);
    ~Employee(void);
 
    // Conversion Constructor
    Employee(const int id);
 
    Employee(const int id, string name);
 
    // Copy Constructor
    Employee(const Employee& emp);
 
 
    // Getter and Setter
    inline const int GetId() const { return m_id; }
    inline void SetId(const int id) { m_id = id; }
 
    inline const string& GetName() const { return m_name; }
    inline void SetName(const string& name) { m_name = name; }
 
    inline const string& GetTitle() const { return m_title; }
    inline void SetTitle(const string& title) { m_title = title; }
 
 
    // Method
    void PrintInfo() const { 
        cout<<"Selected Employee = Id : "<<GetId()<<", name : "<<GetName()<<", title : "<<GetTitle()<<endl;; 
    }
 
private:
    // Member Variable
    int m_id;
    string m_name;
    string m_title;
};
 
// Less Function
struct IDNumberLess: public binary_function<Employee, Employee, bool> 
{
    bool operator() (const Employee& lhs, const Employee& rhs) const
    {
        return lhs.GetId() < rhs.GetId();
    }
};
 
typedef set<Employee, IDNumberLess> EmplDSet;
cs


cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "Employee.h"
 
Employee::Employee(void) : m_id(0), m_name("noname"), m_title("notitle")
{
}
 
Employee::~Employee(void)
{
}
 
Employee::Employee(const int id) : m_id(id), m_name("noname"), m_title("notitle")
{
}
 
Employee::Employee(const int id, string name) : m_id(id), m_name(name), m_title("notitle")
{
}
 
Employee::Employee(const Employee& emp) : m_id(emp.m_id), m_name(emp.m_name), m_title(emp.m_title)
{
}
cs


뭐 대충 이렇게 간단히 Employee 클래스를 작성하고 Set에 사용할 Less function을 작성한다.

Less function에서는 id를 이용하여 값을 비교하여 정렬한다.


main

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include "Employee.h"
using namespace std;
 
int main(int argc, const char* argv[])
{
    EmplDSet se;
 
    Employee e1(1"koko");
    Employee e2(2"momo");
    Employee e3(3"popura");
 
    se.insert(e1);
    se.insert(e2);
    se.insert(e3);
 
    // Method1 : using const_cast
    EmplDSet::iterator it = se.find(1 /* 1 call conversion constructor : Employee temp(1) */);
 
    if (it != se.end()) {
        // Error
        // it->SetTitle("Power Boss");
        // error C2662: 'Employee::SetTitle' : 
        // cannot convert 'this' pointer from 'const Employee' to 'Employee &'
        // Conversion loses qualifiers
 
        // Using const cast
        const_cast<Employee&>(*it).SetTitle("Boss");
        it->PrintInfo();
    }
 
    // Method2 : using Copy constructor and erase
    Employee selectedID(2);
    
    EmplDSet::iterator newIt = se.find(selectedID); // 1.find
    if (newIt != se.end()) {
        Employee e(*newIt); // 2.copy
 
        // avoiding iterator invalidation i++
        se.erase(newIt++); // 3.erase
 
        // 4.edit
        e.SetName("Power Momo");
        e.SetTitle("New Boss");
 
        se.insert(e); // 5.insert
        (se.find(2))->PrintInfo();
    }
 
    return 0;
}
cs


Method2는 책에서 총 5개의 단계로 구구절절 설명하고 있는데

간단하겐 find -> copy -> erase -> edit -> insert 라고 정리하면 되겠다.

728x90
반응형