Equivalent C++ to Python generator pattern

前端 未结 12 2375
Happy的楠姐
Happy的楠姐 2020-11-28 18:42

I\'ve got some example Python code that I need to mimic in C++. I do not require any specific solution (such as co-routine based yield solutions, although they would be acce

12条回答
  •  刺人心
    刺人心 (楼主)
    2020-11-28 18:58

    All answers that involve writing your own iterator are completely wrong. Such answers entirely miss the point of Python generators (one of the language's greatest and unique features). The most important thing about generators is that execution picks up where it left off. This does not happen to iterators. Instead, you must manually store state information such that when operator++ or operator* is called anew, the right information is in place at the very beginning of the next function call. This is why writing your own C++ iterator is a gigantic pain; whereas, generators are elegant, and easy to read+write.

    I don't think there is a good analog for Python generators in native C++, at least not yet (there is a rummor that yield will land in C++17). You can get something similarish by resorting to third-party (e.g. Yongwei's Boost suggestion), or rolling your own.

    I would say the closest thing in native C++ is threads. A thread can maintain a suspended set of local variables, and can continue execution where it left off, very much like generators, but you need to roll a little bit of additional infrastructure to support communication between the generator object and its caller. E.g.

    // Infrastructure
    
    template 
    class Channel { ... };
    
    // Application
    
    using IntPair = std::pair;
    
    void yield_pairs(int end_i, int end_j, Channel* out) {
      for (int i = 0; i < end_i; ++i) {
        for (int j = 0; j < end_j; ++j) {
          out->send(IntPair{i, j});  // "yield"
        }
      }
      out->close();
    }
    
    void MyApp() {
      Channel pairs;
      std::thread generator(yield_pairs, 32, 32, &pairs);
      for (IntPair pair : pairs) {
        UsePair(pair);
      }
      generator.join();
    }
    

    This solution has several downsides though:

    1. Threads are "expensive". Most people would consider this to be an "extravagant" use of threads, especially when your generator is so simple.
    2. There are a couple of clean up actions that you need to remember. These could be automated, but you'd need even more infrastructure, which again, is likely to be seen as "too extravagant". Anyway, the clean ups that you need are:
      1. out->close()
      2. generator.join()
    3. This does not allow you to stop generator. You could make some modifications to add that ability, but it adds clutter to the code. It would never be as clean as Python's yield statement.
    4. In addition to 2, there are other bits of boilerplate that are needed each time you want to "instantiate" a generator object:
      1. Channel* out parameter
      2. Additional variables in main: pairs, generator

提交回复
热议问题