2011-11-25 17 views
6

Per qualche motivo sto iterando su elementi di una classe in un std::set e vorrei modificare leggermente le chiavi, sapendo che l'ordine sarà invariato.Come posso migliorare questo design che mi obbliga a dichiarare una funzione membro const e dichiarare variabili mutabili?

Iteratori su std::set sono const_iterators perché se la chiave viene modificata, potrebbe causare un cattivo ordine e quindi nel danneggiamento dell'insieme. Tuttavia so per certo che le mie operazioni non cambieranno l'ordine dei miei elementi nel set.

Per il momento, ecco la mia soluzione:

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 
} 

Come ti modificarlo (So che potrei usare un std::map ma io sono curioso di sapere se si può suggerire altre opzioni) per rimuovere mutevole e const?

Grazie

+2

Qual è il motivo specifico per cui non si desidera utilizzare le mappe? È per motivi di layout di memoria (guarda gli allocatori?) O ragioni di stile del codice? – sehe

+0

@sehe: Il motivo specifico è che voglio sapere se esistono altre opzioni prima di refactoring del codice. Non escludo completamente il passaggio alle mappe. – Benoit

risposta

8

Non è possibile. Impostare gli elementi sono tenuti ad essere const per contenitori correttezza:

Ti costringe a rendersi conto che il parte chiave ha bisogno di essere immutabile, o le invarianti struttura dei dati sarebbe rotto.

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

Se si voleva mantenere la possibilità di 'esprimere' const-ness nella parte non-chiave, diviso fuori in coppie e memorizzarli in una mappa:

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

o, più flessibilità:

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

Grazie ... ma memorizzare in una mappa era ciò che volevo evitare come espresso nella mia domanda :) Ancora, trovo elegante dichiarare una struttura "valore" all'interno della classe e istanziarla separatamente, quindi +1. – Benoit

+1

@Benoit: una soluzione peggiore è usare l'indirezione, memorizzando un puntatore alla parte "valore" in cui si può avere l'elemento che restituisce un riferimento ad esso su desiderio si avrà la correttezza 'const' corretta. È peggio che usare una 'mappa' per quanto mi riguarda. Di fronte a questo ho spesso preso la controparte della risposta di 'sehe' e definito una struttura' Key' nel mio 'Element', quindi uso' std :: map '(duplicando le informazioni chiave). È importante che la parte 'Key' dell'elemento' non sia modificabile sebbene ... –

1

Un'altra opzione è quella di const_cast ad un tipo di riferimento:

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

Tuttavia, come già detto, non è necessario modificare gli elementi del set.

0

Una possibilità potrebbe essere quella di calcolare la parte del valore di Foo in un 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; 
} 

EDIT: Per essere onesto tuttavia è necessario decidere se l'ulteriore livello di indirezione senso o sarebbe meglio utilizzare una mappa.

Problemi correlati