2011-11-25 38 views
6

由于某种原因,我在std::set中迭代了一个类的元素,并且想要稍微修改这些键,并知道该顺序将保持不变。我该如何改进这种强迫我声明一个成员函数const并声明变量可变的设计?

std::set上的迭代器是const_iterators,因为如果密钥被修改,它可能会导致错误的顺序,从而导致集合损坏。但是我确信我的操作不会改变我的元素在集合中的顺序。

目前,这里是我的解决方案:

class Foo 
{ 
public: 
    Foo(int a, int b): a_(a),b_(b) {} 
    ~Foo(){} 
    bool operator < (const Foo& o) const { return this.a_ < o.a_ ; } 
    void incrementB() const { ++b_; } // <-- the problem: it is not const! 
private: 
    const int a_; 
    mutable int b_;     // <-- I would like to avoid this 
} 

void f() 
{ 
    std::set<Foo> s; 
    // loop and insert many (distinct on a_) Foo elements; 
    std::for_each(s.begin(), c.end(), [](const Foo& s) { s.incrementB(); }); // Foo must be const. iterators are const_iterators 
} 

你将如何修改它(我知道我可以使用一个std::map,但我很好奇,你是否可以建议其他选项),以去除可变和const?

感谢

+2

你不想使用地图的具体原因是什么?是因为内存布局的原因(看分配器?)还是代码风格的原因? – sehe

+0

@sehe:具体原因是我想知道在重构代码之前是否存在其他选项。我并不完全排除切换到地图。 – Benoit

回答

8

你不能。设置元素对于容器正确性必须是const:

它强制您意识到关键部分需要是不可变的,否则数据结构不变量将被破坏。

struct element 
{ 
    std::string key_part; // const in the set 

    bool operator<(const element&o) const { return key_part<o.key_part; } 

    private: 
    mutable int m_cached; // non-key, *NOT* used in operator< 
}; 

如果你想保留的可能性,“表达”在非关键部分常量性,把它分解出来成对并将其存储在一个地图:

std::map<std::string /*key_part*/, int /*m_cached*/> mapped; 

,或者更灵活:

struct element 
{ 
    std::string key_part; // const in the set 

    bool operator<(const element&o) const { return key_part<o.key_part; } 

    struct value { 
     int m_cached; 
     int m_moredata; //... 
    } /*not in the element itself*/; 
}; 

std::map<element, element::value> mapped; 
+0

谢谢......但在地图中存储是我想要避免的问题:)尽管如此,我发现在类中声明一个“值”结构并单独实例化它是优雅的,所以+1。 – Benoit

+1

@Benoit:更糟糕的解决方案是使用间接方式,通过存储指向“值”部分的指针,您可以让元素返回对其的引用,希望您具有正确的“const”正确性。就我而言,它比使用“地图”更糟糕。当面对这个问题时,我经常拿着'sehe'的答案对应并在我的'Element'中定义了一个'Key'结构,然后我使用了一个'std :: map '(复制关键信息)。元素的'Key'部分是不可修改的,但是重要的是... –

1

另一种选择是const_cast为引用类型:

class Foo 
{ 
public: 
    void incrementB() const { ++ const_cast< int& >(b_); } 
private: 
    int b_; 
}; 

但正如sehe已经说过的,你不应该修改set的元素。

0

一种可能性可能是在一个pimpl中分解Foo的价值部分。

class Element 
{ 
public: 

    Element(int key, int value); 

    Element(const Element& el); 
    Element(Element&& el); 

    ~Element(); 

    bool operator < (const Element& o) const; 

    void incrementValue() const; 
    int getValue() const; 

private: 

    Element& operator=(const Element&); 
    Element& operator=(Element&& el); 

    struct Key 
    { 
     Key(const int key) : m_KeyValue(key) 
     { 
     }; 

     const int m_KeyValue; 
    }; 

    struct Value; 

    const Key     m_Key; 
    std::unique_ptr<Value>  m_Value; 

}; 

struct Element::Value 
{ 
    Value(int val) : value(val) 
    { 

    } 

    int value; 
}; 

Element::Element(int key, int value) : 
    m_Key(key), 
    m_Value(new Element::Value(value)) 
{ 

} 

Element::~Element() 
{ 

} 

Element::Element(const Element& el) : 
    m_Key(el.m_Key), 
    m_Value(new Element::Value(*el.m_Value)) 
{ 

} 

Element::Element(Element&& el) : 
    m_Key(el.m_Key) 
{ 
    m_Value = std::move(el.m_Value); 
    el.m_Value.release(); 
} 

bool Element::operator < (const Element& o) const 
{ 
    return m_Key.m_KeyValue < o.m_Key.m_KeyValue; 
} 

void Element::incrementValue() const 
{ 
    m_Value->value++; 
} 

int 
Element::getValue() const 
{ 
    return m_Value->value; 
} 

void f() 
{ 
    std::set<Element> s; 

    s.insert(Element(1,2)); 
    s.insert(Element(2,3)); 

    std::for_each(s.begin(), s.end(), [](const Element& s) { s.incrementValue(); }); 

    std::for_each(s.begin(), s.end(), [](const Element& s) 
    { 
     std::cout << s.getValue() << std::endl; 

    }); 
} 

int 
main() 
{ 
    f(); 
    return 0; 
} 

编辑:说实话,但你必须决定是否额外的间接水平是有道理的,或者你会更好地使用地图。