2013-01-22 9 views
11

Próbowałem napisać kod do tworzenia sekwencji w stylu funkcjonalnym. Napisałem jedną funkcję, range(a, b), która zwraca obiekt, który można powtórzyć, foreach-style, aby przejść przez liczby a, a + 1, ..., b - 1. Następnie napisałem inną funkcję, map(f, t), która zwraca inny iterowalny obiekt, w którym każdy element w sekwencji jest wynikiem wywołania f z odpowiednim elementem obiektu iterowalnego t.Dlaczego gcc optymalizuje tę pętlę foreach C++ 11 za pomocą mojego niestandardowego iteratora?

Działa to zgodnie z oczekiwaniami, jeśli kompiluję przy użyciu -O1 lub niższej; z -O2 lub wyższą, moja pętla foreach (w main na dole) zostaje całkowicie zoptymalizowana i nic nie jest drukowane. Dlaczego tak się dzieje, co zrobiłem źle? Oto mój kod:

template<typename T> 
struct _range { 
    T a; 
    T b; 

    _range(T a, T b): 
     a(a), 
     b(b) 
    { 
    } 

    struct iterator { 
     T it; 

     iterator(T it): 
      it(it) 
     { 
     } 

     bool operator!=(const iterator &other) const 
     { 
      return it != other.it; 
     } 

     void operator++() 
     { 
      ++it; 
     } 

     T operator*() const 
     { 
      return it; 
     } 
    }; 

    iterator begin() const 
    { 
     return iterator(a); 
    } 

    iterator end() const 
    { 
     return iterator(b); 
    } 
}; 

template<typename T> 
_range<T> range(const T a, const T b) 
{ 
    return _range<T>(a, b); 
} 

template<typename F, typename T> 
struct _map { 
    const F &f; 
    const T &t; 

    _map(const F &f, const T &t): 
     f(f), 
     t(t) 
    { 
    } 

    struct iterator { 
     const F &f; 
     typename T::iterator it; 

     iterator(const F &f, typename T::iterator it): 
      f(f), 
      it(it) 
     { 
     } 

     bool operator!=(const iterator &other) const 
     { 
      return it != other.it; 
     } 

     void operator++() 
     { 
      ++it; 
     } 

     int operator*() const 
     { 
      return f(*it); 
     } 
    }; 

    iterator begin() const 
    { 
     return iterator(f, t.begin()); 
    } 

    iterator end() const 
    { 
     return iterator(f, t.end()); 
    } 
}; 

template<typename F, typename T> 
_map<F, T> map(const F &f, const T &t) 
{ 
    return _map<F, T>(f, t); 
} 

#include <algorithm> 
#include <cstdio> 

int main(int argc, char *argv[]) 
{ 
    for (int i: map([] (int x) { return 3 * x; }, range(-4, 5))) 
     printf("%d\n", i); 

    return 0; 
} 
+0

Może błąd? Z Clang ++, działa dobrze zarówno na poziomie optymalizacji O1 i O2. –

+7

Staraj się, aby '_map' zapisywał swoich członków według wartości, zamiast przechowywać const refs. (Podejrzewam, że twój obiekt "zasięgu" jest niszczony wcześniej, niż się spodziewaliście.) – ildjarn

+3

Wierzę, że @ildjarn ma rację: tymczasowy jest zmuszony żyć tak długo, jak długo stałe odniesienie do niego jest żywe. odniesienie, do którego się odnosi, jest argumentem konstruktora 'map'. gdy konstruktor wraca, odwołanie wykracza poza zakres i tymczasowy zostaje zniszczony. –

Odpowiedz

8

Podsumowując dotychczasowe uwagi:

range(-4, 5) tworzy tymczasowy, oraz (w większości przypadków) tymczasowych tylko na żywo, aż do zakończenia pełnego ekspresji, w którym zostały utworzone. Tak więc w twoim przypadku zwrócony obiekt _range jest ważny podczas konstrukcji _map, ale gdy tylko zostanie zwrócone _map z , kończy się pełne wyrażenie, a obiekt _range zostanie zniszczony.

To powiedziawszy, ponieważ _map posiada argumenty przekazywane do konstruktora przez const ref zamiast przez wartość, oznacza to, że do czasu zakres oparte for rozpoczyna wykonywanie Twój _map::t jest już zwisające odniesienia - klasyczny undefined behavior.

Aby to naprawić, wystarczy, że przechowisz elementy danych według wartości przez _map.