2010-11-12 33 views
5

Mam klasy Foo że zawiera map i zapewnia begin() i end() funkcje iteracyjne nad nim:Jak dostosować iterator zestawu, aby zachowywał się jak iterator mapy?

class Foo { 
    typedef std::map<int, double> Container; 
    typedef Container::const_iterator const_iterator; 
    Container c_; 
public: 
    const_iterator begin() const { return c_.begin(); } 
    const_iterator end() const { return c_.end(); } 
    void insert(int i, double d) { c_[i] = d; } 
    // ... 

}; 

Teraz chciałbym zmienić go wewnętrznie od std::map<int, double> do zaledwie std::set<int>, ale nie chcę złamać dowolny kod klienta.

Teraz funkcja double d w funkcji insert zostanie po prostu zignorowana. I następujący kod powinien nadal być ważne, gdzie it->second będzie teraz po prostu zawsze być 0.0:

Foo foo; 
for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) { 
    std::cout << it->first << " " << it->second << std::endl; 
} 

Jak mogę dokonać tych zmian w klasie Foo?

Innymi słowy, w jaki sposób mogę zapewnić Foo::const_iterator, który dostosowuje nowy wewnętrzny std::set<int>::const_iterator, aby zachowywał się jak stary std::map<int,double>::const_iterator?

AKTUALIZACJA: Powodem, dla którego chcę pozbyć się map jest wydajność pamięci. Mam miliony instancji Foo i nie mogę pozwolić sobie na zapisanie w nich wartości double.

+3

Szczerze mówiąc, to brzmi jak naprawdę zły pomysł. Utrzymywanie zgodności umowy, gdy semantyka całkowicie się zmieniła, nie ma znaczenia. –

+1

@KonradRudolph C++ robi to od pierwszego dnia. – wilhelmtell

+0

@wilhelm: To prawda. Ale nie ma powodu, by robić to samo. –

Odpowiedz

0

Być może coś wzdłuż linii

operator int()(const std::pair<int, double>& p) const { 
    return p.first; 
} 

może w pewnym owijki?

2

Byłoby użyciu

std::set<std::pair<int, double> > 

nie być wystarczające do tego porównywalności?

W przeciwnym razie zawsze można napisać własny iterator, który otacza Iterator std :: list i zapewnia członkom first i second. Zasadniczo twój operator ++ wywoływał operatora ++ na prawdziwym iteratorze itp., A operator odwołujący mógł zwrócić tymczasową std :: pair (według wartości) lub odniesienie do std :: pair, która żyje w samym iteratorze (jeśli twoja spuścizna kod może sobie z tym poradzić).

Update, lekko wymyślony przykład, może działać w zależności od scenariusza:

#include <iostream> 
#include <set> 

class Foo { 
    typedef std::set<int> Container; 
    typedef Container::const_iterator legacy_iterator; 
    Container c_; 

    // legacy iterator doesn't have a virtual destructor (probably?), shouldn't 
    // be a problem for sane usage though 
    class compat_iterator : public legacy_iterator { 
    public: 
    compat_iterator(const legacy_iterator& it) : legacy_iterator(it) { 
    } 

    const std::pair<int,double> *operator->() const { 
     static std::pair<int,double> value; 
     value = std::make_pair(**this, 0.0); 
     // Not meeting the usual semantics! 
     return &value; 
    } 
    }; 
public: 
    typedef compat_iterator const_iterator; 

    const_iterator begin() const { return c_.begin(); } 
    const_iterator end() const { return c_.end(); } 

}; 



int main() { 

    Foo foo; 
    for(Foo::const_iterator it = foo.begin(); it != foo.end(); ++it) { 
    std::cout << it->first << " " << it->second << std::endl; 
    } 

} 
+0

Nie, powinienem był powiedzieć: Powodem, dla którego potrzebuję teraz "zestawu", jest chęć zaoszczędzenia pamięci. Mam miliony instancji 'Foo' i nie mogę już dłużej przechowywać podwójnego. – Frank

+0

Zaktualizowałem swoją odpowiedź, aby dodać przykład czegoś, co implementuje zgodność z minimalnymi dodatkowymi kosztami. Oczywiście przyjęcie adresu któregokolwiek z członków tej pary byłoby teraz złym pomysłem, ale jeśli starszy kod na nim nie polega, to może rozwiązać twój problem. – Flexo

0

Być może można zdefiniować fake_pair klasy, która implementuje first i second i umieścić set<fake_pair> wewnątrz Foo.

1

Co powiesz na coś takiego?

#include <iostream> 
#include <map> 
#include <set> 

struct Funky 
{ 
    int first; 
    static const double second; 

    Funky(int i) 
    : first(i) 
    {} 
}; 

const double Funky::second = 0.0; 

bool operator<(const Funky& lhs, const Funky& rhs) 
{ 
    return lhs.first < rhs.first; 
} 

class Foo 
{ 
private: 
    //std::map<int,double> m_data; 
    std::set<Funky> m_data; 
public: 
    //typedef std::map<int,double>::const_iterator const_iterator; 
    typedef std::set<Funky>::const_iterator const_iterator; 

    const_iterator begin() const 
    { 
     return m_data.begin(); 
    } 

    const_iterator end() const 
    { 
     return m_data.end(); 
    } 

    void insert(int i, double d) 
    { 
     //m_data.insert(std::make_pair(i, d)); 
     m_data.insert(i); 
    } 
}; 

int main() 
{ 
    Foo foo; 
    foo.insert(23, 9.0); 
    for(Foo::const_iterator it=foo.begin(), iend=foo.end(); it!=iend; ++it) 
    { 
     std::cout << it->first << ' ' << it->second << '\n'; 
    } 
    return 0; 
} 
+0

Jedną rzeczą, na którą powinienem zwrócić uwagę, jest to, że wszystko, co próbuje zmienić 'it-> second', teraz zawiedzie - więc możesz chcieć pozbyć się stałej. Alternatywnie, możesz chcieć, aby ludzie wiedzieli, co się dzieje ... –

+0

(Myślę, że cały pomysł robienia tego typu rzeczy jest raczej nierozważny, dla nagrania ...) –

+0

Nie sądzę, że redefinicja '' operator <'jest konieczny, domyślny powinien być wystarczający. –

0

Nie możesz, nie całkiem. Problem polega na tym, że zmieniasz interfejs, który zawsze będzie łamał twoich klientów. Polecam utworzyć dwie nowe funkcje newBegin i newEnd (lub podobnych), które mają nowe zachowanie. Twój stary interfejs zachowuje to samo, ale oznaczyć go jako stracony. Wdrożenie tego starego interfejsu można wykorzystać do pracy opisanej przez innych.