Как я могу улучшить этот дизайн, который заставляет меня объявлять функцию-член const и объявлять переменные непостоянными? - PullRequest
6 голосов
/ 25 ноября 2011

По какой-то причине я перебираю элементы класса в std::set и хотел бы немного изменить ключи, зная, что порядок не изменится.

Итераторы на 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, но мне интересно, можете ли вы предложить другие варианты), чтобы удалить mutable и const?

Спасибо

Ответы [ 3 ]

8 голосов
/ 25 ноября 2011

Вы не можете.Элементы набора должны быть константными для правильности контейнера:

Это заставляет вас осознать, что ключевая часть должна быть неизменной, иначе инварианты структуры данных будут нарушены.

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;
1 голос
/ 25 ноября 2011

Другой вариант - const_cast для ссылочного типа:

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

Но, как уже говорилось, вам не следует изменять элементы набора.

0 голосов
/ 25 ноября 2011

Одной из возможностей может быть выделение части значения Foo в pimpl.

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;
}

РЕДАКТИРОВАТЬ: Однако, если честно, вы должны решить, имеет ли смысл дополнительный уровень косвенности или вам лучше использовать карту.

...