2011-08-03 9 views
7

przykłady pokazujące jak iteracyjne nad std::map są często tak:++ it it it ++ podczas iteracji na mapie?

MapType::const_iterator end = data.end(); 
for (MapType::const_iterator it = data.begin(); it != end; ++it) 

tj używa ++it zamiast it++. Czy istnieje jakiś powód, dlaczego? Czy może być jakiś problem, jeśli użyję zamiast tego it++?

+0

Zobacz [to] [1]. [1]: http://stackoverflow.com/questions/24853/c-what-is-the-difference-between-i-and-i – sergio

+5

Jeśli kod w C++ przez dłuższy czas , '++ it' zapewni znaczny wzrost wydajności. Nie w twoim kodzie, ale w czasie spędzonym na wyjaśnianiu ludziom, dlaczego nie robiłeś '++ it' w pierwszej kolejności. Ponieważ to naprawdę nie ma znaczenia w najmniejszym stopniu, równie dobrze możesz zrobić to w sposób, który nie doprowadzi do kłótni. –

+0

@Dennis: Nie ma znaczenia * dużo * i bardzo często nie da się zmierzyć, ale w ciasnych pętlach może to mieć znaczenie. –

Odpowiedz

10

Umieszczenie go na próbę, zrobiłem trzy pliki Źródło:

#include <map> 

struct Foo { int a; double b; char c; }; 

typedef std::map<int, Foo> FMap; 

### File 1 only ### 

void Set(FMap & m, const Foo & f) 
{ 
    for (FMap::iterator it = m.begin(), end = m.end(); it != end; ++it) 
    it->second = f; 
} 

### File 2 only ### 

void Set(FMap & m, const Foo & f) 
{ 
    for (FMap::iterator it = m.begin(); it != m.end(); ++it) 
    it->second = f; 
} 

### File 3 only ### 

void Set(FMap & m, const Foo & f) 
{ 
    for (FMap::iterator it = m.begin(); it != m.end(); it++) 
    it->second = f; 
} 

### end ### 

Po kompilacji z g++ -S -O3, GCC 4.6.1, uważam, że wersja 2 i 3 produce identyczne montaż oraz wersja 1 różni tylko w jednej instrukcji, cmpl %eax, %esi vs cmpl %esi, %eax.

Wybieraj i używaj wszystkiego, co pasuje do Twojego stylu. Prefiks przyrostowy ++it jest prawdopodobnie najlepszy, ponieważ najdokładniej wyraża twoje wymagania, ale nie rozłączaj się z tym.

+1

Dobra prezentacja optymalizacji, ale to może działać tylko dla wystarczająco prostego i zainicjowanego operatora '' ++ '. Dobrze widzieć, że g ++ może to zrobić dla 'std :: map'. –

+1

@ Christopher: Tak, i zdecydowanie powinieneś zawsze używać '++ it', kiedy o to ci chodzi. Po prostu miałem ochotę przedstawić twarde porównanie na temat prawdziwego wpływu dwóch często reklamowanych punktów (podnosząc koniec i stosując przyrost prefiksu). –

20

it++ zwraca kopię poprzedniej iteracyjnej. Ponieważ ten iterator nie jest używany, jest to marnotrawstwem. ++it zwraca odwołanie do inkrementowanego iteratora, unikając jego kopii.

proszę zobaczyć Question 13.15 na pełniejsze wyjaśnienie.

+4

To nie będzie miało znaczenia. Kwestia jest czysto stylu. –

+1

@Dark - ponieważ nie jest używany, prawdopodobnie zauważy to kompilator i zoptymalizuje kopiowanie. –

+2

Jestem tego świadomy. Oczywiście kompilator nie musi tego robić. –

7

Jest niewielki performance advantage w użyciu operatorów preinkrementuj kontra operatorów postinkrementacja. W tworzeniu pętle, które używają iteratory, należy zdecydować się na stosowanie wstępnych przyrostów:

for (list<string>::const_iterator it = tokens.begin(); 
    it != tokens.end(); 
    ++it) { // Don't use it++ 
    ... 
} 

Powodem wychodzi na jaw, gdy myślisz o tym, jak obaj operatorzy będą typowo implemented.The preinkrementuj jest dość prosta. Jednak, aby do post-przyrostu do pracy, trzeba najpierw wykonać kopię obiektu, czy rzeczywisty przyrost na oryginalny obiekt, a następnie powrót do kopiowania:

class MyInteger { 
private: 
    int m_nValue; 

public: 
    MyInteger(int i) { 
     m_nValue = i; 
    } 

    // Pre-increment 
    const MyInteger &operator++() { 
     ++m_nValue; 
     return *this; 
    } 

    // Post-increment 
    MyInteger operator++(int) { 
     MyInteger clone = *this; // Copy operation 1 
     ++m_nValue; 
     return clone; // Copy operation 2 
    } 
} 

Jak widać, w słupek - Implementacja inkrementacji obejmuje dwie dodatkowe operacje kopiowania. Może to być dość kosztowne, jeśli przedmiot, o którym mowa, jest nieporęczny. Powiedziawszy to, niektóre kompilatory mogą być wystarczająco inteligentne, aby uciec z jednym procesem kopiowania, poprzez optymalizację. Chodzi o to, że post-inkrementacja zwykle wymaga więcej pracy niż wstępnego inkrementu, a zatem rozsądnie jest przyzwyczaić się do umieszczania "++" przed iteratorami, a nie później.

(1) Kredyt na połączoną stronę.

6

Z logicznego punktu widzenia - jest taki sam i nie ma znaczenia tutaj.

Dlaczego prefiks jeden jest używany - ponieważ jest szybszy - zmienia iterator i zwraca jego wartość, podczas gdy przyrostek tworzy obiekt tymczasowy, inkrementuje bieżący iterator, a następnie zwraca obiekt tymczasowy (kopię tego samego iteratora, przed inkrementacja). Ponieważ nikt nie obserwuje tego obiektu tymczasowego (wartość zwracana), jest on taki sam (logicznie).

Istnieje spora szansa, że ​​kompilator zoptymalizuje to.


Ponadto - w rzeczywistości, to ma być tak przez wszelkich typów w ogóle. Ale tak powinno być. Ponieważ każdy może przeciążyć operator++ - przyrostek i przedrostek, mogą mieć efekty uboczne i inne zachowanie.

Cóż, to jest okropne, ale wciąż możliwe.

+0

Rzeczywiście straszne. Coś w rodzaju nadużywania operatora zmiany do umieszczania danych w strumieniu, prawda? ;-) (SCNR) –

+0

Cóż, to jest dobry punkt - w końcu możesz mieć do czynienia z niestandardowym iteratorem, który jest bardzo drogi do skopiowania z jakiegokolwiek powodu i nie można go zoptymalizować, więc zwyczajnie pisz co masz na myśli ('++ it'), a nie to, do czego mógłbyś się przyzwyczaić (" C++ "), to po prostu dobra praktyka. –

+0

@Kerrek SB - dokładnie :) Zawsze wolę '++ bla_bla', kiedy można go używać. –

1

nie spowoduje żadnych problemów, ale przy użyciu ++it jest bardziej poprawna.Z małych typów to nie ma znaczenia, czy do korzystania ++ii++, ale dla „dużych” klas:

operator++(type x,int){ 
    type tmp=x; //need copy 
    ++x; 
    return tmp; 
} 

Kompilator może zoptymalizować niektóre z nich, ale trudno mieć pewność.

1

Jak powiedzieli inni, wolą ++, chyba że nie będzie działać w danym kontekście. Dla iteracji nad kontenerami małych typów naprawdę robi małą różnicę (lub bez różnicy, czy kompilator ją optymalizuje), ale w przypadku pojemników o dużych typach może to mieć znaczenie z powodu oszczędności kosztów wykonania kopii.

Prawdą jest, że możesz znać w swoim konkretnym kontekście, że typ jest na tyle mały, że nie musisz się tym martwić. Ale później ktoś inny w twoim zespole może zmienić zawartość kontenera tam, gdzie ma to znaczenie. Plus, myślę, że lepiej jest wejść w dobry nałóg, a post-inkrementować tylko wtedy, gdy wiesz, że musisz.

Powiązane problemy