Mam przykładowy kod w Pythonie, który muszę naśladować w C ++. Nie potrzebuję żadnego konkretnego rozwiązania (np. Rozwiązania oparte na wspólnych rutynach wydajności, chociaż byłyby one również akceptowalnymi odpowiedziami), po prostu muszę w jakiś sposób odtworzyć semantykę.
Pyton
Jest to podstawowy generator sekwencji, wyraźnie zbyt duży, aby przechowywać zmaterializowaną wersję.
def pair_sequence():
for i in range(2**32):
for j in range(2**32):
yield (i, j)
Celem jest zachowanie dwóch wystąpień powyższej sekwencji i iteracja po nich w pół-lockstep, ale w fragmentach. W poniższym przykładzie first_pass
używa sekwencji par do zainicjowania bufora, a następnie second_pass
odtwarza tę samą dokładną sekwencję i ponownie przetwarza bufor.
def run():
seq1 = pair_sequence()
seq2 = pair_sequence()
buffer = [0] * 1000
first_pass(seq1, buffer)
second_pass(seq2, buffer)
... repeat ...
C ++
Jedyną rzeczą, jaką mogę znaleźć dla rozwiązania w C ++, jest naśladowanie za yield
pomocą coroutines C ++, ale nie znalazłem żadnego dobrego odniesienia, jak to zrobić. Interesują mnie również alternatywne (nie ogólne) rozwiązania tego problemu. Nie mam wystarczającej ilości pamięci, aby zachować kopię sekwencji między przejściami.
Odpowiedzi:
Generatory istnieją w C ++, pod inną nazwą: Iteratory wejściowe . Na przykład czytanie z
std::cin
jest podobne do posiadania generatorachar
.Musisz po prostu zrozumieć, co robi generator:
W twoim banalnym przykładzie jest to dość łatwe. Koncepcyjnie:
Oczywiście opakowujemy to jako odpowiednią klasę:
Więc hum yeah ... może być tak, że C ++ jest odrobinę bardziej rozwlekły :)
źródło
W C ++ są iteratory, ale implementacja iteratora nie jest prosta: należy zapoznać się z koncepcjami iteratorów i dokładnie zaprojektować nową klasę iteratora, aby je zaimplementować. Na szczęście Boost ma szablon iterator_facade, który powinien pomóc we wdrożeniu iteratorów i generatorów kompatybilnych z iteratorami.
Czasami do zaimplementowania iteratora można użyć programu bez stosu .
PS Zobacz także ten artykuł, który wspomina zarówno o
switch
włamaniu Christophera M. Kohlhoffa, jak i Boost.Coroutine autorstwa Olivera Kowalke. Praca Oliver KOWALKE jest to nawiązanie na Boost.Coroutine Giovanni P. Deretta.PS Myślę, że można też napisać coś w rodzaju generatora z lambdami :
Lub z funktorem:
PS Oto generator zaimplementowany w korektach Mordoru :
źródło
Ponieważ Boost.Coroutine2 obsługuje go teraz bardzo dobrze (znalazłem go, ponieważ chciałem rozwiązać dokładnie ten sam
yield
problem), publikuję kod C ++, który pasuje do twojego pierwotnego zamiaru:W tym przykładzie
pair_sequence
nie przyjmuje dodatkowych argumentów. Jeśli to konieczne,std::bind
lub należy użyć lambdy do wygenerowania obiektu funkcji, który przyjmuje tylko jeden argument (ofpush_type
), kiedy jest przekazywany docoro_t::pull_type
konstruktora.źródło
Wszystkie odpowiedzi, które wymagają napisania własnego iteratora, są całkowicie błędne. Takie odpowiedzi całkowicie pomijają sens generatorów Pythona (jedna z największych i unikalnych funkcji języka). Najważniejszą rzeczą dotyczącą generatorów jest to, że wykonanie rozpoczyna się tam, gdzie zostało przerwane. Nie dzieje się tak w przypadku iteratorów. Zamiast tego należy ręcznie przechowywać informacje o stanie w taki sposób, że gdy operator ++ lub operator * zostanie wywołany od nowa, właściwa informacja będzie umieszczona na samym początku następnego wywołania funkcji. Dlatego pisanie własnego iteratora C ++ jest gigantycznym problemem; podczas gdy generatory są eleganckie i łatwe do odczytania + zapisu.
Nie sądzę, że istnieje dobry analog dla generatorów Pythona w natywnym C ++, przynajmniej jeszcze nie teraz (istnieje rummor, że wydajność wyląduje w C ++ 17 ). Możesz uzyskać coś podobnego, odwołując się do strony trzeciej (np. Sugestii Yongwei's Boost) lub tocząc własną.
Powiedziałbym, że najbliższą rzeczą w natywnym C ++ są wątki. Wątek może utrzymywać zawieszony zestaw zmiennych lokalnych i może kontynuować wykonywanie od miejsca, w którym został przerwany, podobnie jak generatory, ale musisz rzucić trochę dodatkowej infrastruktury, aby obsługiwać komunikację między obiektem generatora a jego wywołującym. Na przykład
To rozwiązanie ma jednak kilka wad:
źródło
Prawdopodobnie powinieneś sprawdzić generatory w std :: experimental w Visual Studio 2015 np .: https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
Myślę, że to jest dokładnie to, czego szukasz. Ogólnie generatory powinny być dostępne w C ++ 17, ponieważ jest to tylko eksperymentalna funkcja Microsoft VC.
źródło
Jeśli musisz to zrobić tylko dla stosunkowo niewielkiej liczby określonych generatorów, możesz zaimplementować każdy z nich jako klasę, w której dane składowe są równoważne zmiennym lokalnym funkcji generatora w języku Python. Następnie masz następną funkcję, która zwraca następną rzecz, którą wygenerowałby generator, aktualizując stan wewnętrzny, gdy to robi.
Wydaje mi się, że jest to zasadniczo podobne do sposobu implementacji generatorów Pythona. Główną różnicą jest to, że potrafią zapamiętać przesunięcie w kodzie bajtowym funkcji generatora jako część „stanu wewnętrznego”, co oznacza, że generatory można zapisać jako pętle zawierające plony. Zamiast tego musiałbyś obliczyć następną wartość z poprzedniej. W przypadku twojego
pair_sequence
jest to dość trywialne. Może to nie być dla złożonych generatorów.Potrzebujesz również sposobu wskazania zakończenia. Jeśli to, co zwracasz, jest „podobne do wskaźnika”, a NULL nie powinno być prawidłową wartością uzyskiwaną, możesz użyć wskaźnika NULL jako wskaźnika zakończenia. W przeciwnym razie potrzebny jest sygnał poza pasmem.
źródło
Coś takiego jest bardzo podobne:
Używanie operatora () to tylko kwestia tego, co chcesz zrobić z tym generatorem, możesz również zbudować go jako strumień i upewnić się, że dostosowuje się na przykład do istream_iterator.
źródło
Korzystanie z range-v3 :
źródło
Coś jak to :
Przykładowe zastosowanie:
Drukuje liczby od 0 do 99
źródło
Cóż, dzisiaj również szukałem łatwej implementacji kolekcji w C ++ 11. Właściwie byłem rozczarowany, ponieważ wszystko, co znalazłem, jest zbyt dalekie od takich rzeczy, jak generatory Pythona lub operator C # wydajności ... lub zbyt skomplikowane.
Celem jest wykonanie kolekcji, która będzie emitować swoje przedmioty tylko wtedy, gdy jest to wymagane.
Chciałem, żeby było tak:
Znalazłem ten post, najlepsza odpowiedź IMHO dotyczyła boost.coroutine2 autorstwa Yongwei Wu . Ponieważ jest najbliżej tego, czego chciał autor.
Warto się uczyć kurtyn doładowania .. I chyba będę robić w weekendy. Ale póki co używam mojej bardzo małej implementacji. Mam nadzieję, że pomoże to komuś innemu.
Poniżej przykład użycia, a następnie realizacji.
Przykład.cpp
Generator.h
źródło
Ta odpowiedź działa w C (i dlatego myślę, że działa również w C ++)
Jest to prosty, niezorientowany obiektowo sposób naśladowania generatora. To działało zgodnie z oczekiwaniami.
źródło
Podobnie jak funkcja symuluje koncepcję stosu, tak generatory symulują koncepcję kolejki. Reszta to semantyka.
Na marginesie, zawsze można symulować kolejkę ze stosem, używając stosu operacji zamiast danych. W praktyce oznacza to, że można zaimplementować zachowanie podobne do kolejki, zwracając parę, której druga wartość ma następną funkcję do wywołania lub wskazuje, że nie mamy już żadnych wartości. Ale to jest bardziej ogólne niż to, co robi wydajność vs zwrot. Pozwala na symulację kolejki dowolnych wartości zamiast jednorodnych wartości, których oczekujesz od generatora, ale bez utrzymywania pełnej kolejki wewnętrznej.
Dokładniej, ponieważ C ++ nie ma naturalnej abstrakcji dla kolejki, musisz użyć konstrukcji, które wewnętrznie implementują kolejkę. Zatem odpowiedzią, która podała przykład z iteratorami, jest przyzwoita implementacja koncepcji.
W praktyce oznacza to, że możesz zaimplementować coś z funkcjonalnością kolejki bare-bones, jeśli chcesz tylko czegoś szybkiego, a następnie konsumujesz wartości kolejki tak, jak zużywałbyś wartości uzyskane z generatora.
źródło