Comments 9
Если честно объяснение непонятное с самой первой структуры magicians_hat_base.
Int state_ - у структуры 4294967295 состояний? Или всё-таки какие-то из них недостижимые? Или структура сама не знает, какие состояния достижимые и какие нет? Подобные соглашения должны быть в описании класса.
" // Функция возобновления virtual void pullout_rabbit() " - мне казалось что таким именем будет названа функция вытаскивания кролика, а не функция "возобновления" чего-либо? Почему "возобновления"? Если я сделаю pullout_rabbit(); pullout_rabbit(); это 2 раза возобновить что-то? Это ошибка или нет?
Int current_rabbit_ - зачем заводить в структуре поле "вытащенный кролик" если "вытащенный кролик" это по смыслу возвращаемое значение, а не хранимое? В базовом классе нет никакой логики завязанной на это поле, просто взяли в стиле C завели поле без знания как им управлять и без гарантий что потомок это реализует.
Что такое состояния 1, 0, как пользователь должен догадаться что из них что означает? Pullout_rabbit() можно вызывать бесконечно много раз - это баг или фича? Разве pullout_rabbit не должен вести себя по-разному в зависимости от done()?
Да, с базовым примером мною копнуто глубоко, аж до 1998 года, туда не только лишь всё могут. У базовой структуре нет состояний, есть поле, в котором наследники могут хранить любое своё состояние. Базовый класс знает только то, что 0 - завершено. И сколько после этого pullout_rabbit не вызывай , он ничего больше не сделает. Если чуть внимательнее рассмотрите код, сами поймете. Довольно странно ждать от примера для пояснения низкоуровневых механизмов высоких уровней абстракции, наоборот от них стараются избавиться, чтобы показать суть.
Добавлю к предыдущему ответу. В каждом коде есть кнопка "Объяснить код с SourceCraft". Вот что она мне например выдала:
Представленный фрагмент кода на C++ описывает структуру magicians_hat_base, которая служит базовым интерфейсом для работы с «шляпой фокусника». Эта структура предназначена для реализации генератора, который можно возобновлять и проверять на завершённость.
Структура содержит:
Поле state_ для хранения текущего состояния шляпы.
Поле current_rabbit_ для хранения очередного вытащенного «кролика».
Конструктор, инициализирующий состояние шляпы.
Виртуальный деструктор для корректного уничтожения объектов.
Виртуальную функцию pullout_rabbit для возобновления работы генератора.
Функцию done для проверки завершённости работы генератора.
Функцию destroy для уничтожения объекта.
Этот код демонстрирует подход к реализации корутин в C++ до появления корутин в C++20, используя техники, доступные в C++98. Он позволяет создавать генераторы, которые можно приостанавливать и возобновлять, сохраняя состояние между вызовами.
Представленный фрагмент кода на C++ описывает структуру some_hat, которая наследуется от базового класса magicians_hat_base. Эта структура предназначена для реализации генератора, аналогичного корутине в Python, который генерирует последовательность чисел с заданным шагом.
В some_hat определены поля класса для хранения начальных параметров генератора (start_, end_, step_) и текущего значения (rabbit_). Конструктор инициализирует эти поля переданными значениями. Виртуальная функция pull_rabbit предназначена для реализации логики генератора, которая будет переопределена в дочернем классе. Этот код демонстрирует подход к реализации корутин в C++ до появления корутин в C++20, используя техники, доступные в C++98. Он позволяет понять, как можно реализовать генератор с сохранением состояния между вызовами, используя классы и виртуальные функции.
Данный фрагмент кода на C++ демонстрирует реализацию генератора, который имитирует поведение корутины. Метод pullout_rabbit использует состояние (state_) для управления выполнением и имитирует ключевое слово yield из Python, позволяя приостановить выполнение и возобновить его с того же места при следующем вызове. В методе используется switch для управления потоком выполнения в зависимости от текущего состояния (state_). В каждом случае (case 1, case 2) происходит установка значения current_rabbit_ и изменение состояния, что позволяет продолжить выполнение с нужного места при следующем вызове метода. Этот подход демонстрирует, как можно реализовать генератор в C++ до появления корутин в C++20, используя состояния и переменные класса для сохранения контекста выполнения между вызовами.
Я ни на что не намекаю, но даже ИИ смог разобраться с этим примером кода.
В этом высере ИИ, которым вы судя по всему подменяете своё мнение, не адресован ни один из моих вопросов выше, которые я бы задал, будь я незнакомым с корутинами и прочитав декларацию базового класса.
Ваши вопросы мне напоминают человека, которому для примера схематичного устройства автомобиля показывают карт, а он спрашивает - "а почему одноместный? Где прикуриватель и дифференциал? Как тут кондиционер может работать, если окон, которые надо закрыть, тут вообще нет? ".
Возможно, вам сама концепция "генераторов" совсем не знакома.
Да вообще ужас, безумных вещей каких-то требую, например чтобы функция "достать кролика" возвращала объект кролик. А возвращаемое генератором значение у вас является частью состояния генератора. Вы уверены, что проблемы с пониманием генераторов у меня, а не у вас?
Вы когда ручку игрового автомата дёргаете, выигрыш из ручки выпадает, или из лотка? Я бы мог конечно сделать std::optional<int> pullout_rabbit (), но это совсем не то, как работает механизм корутин в C++. У них как раз ручка resume отдельно, результат отдельно. Именно это и должен показать этот пример, не то, как бы вам хотелось, а то, как оно есть.
Ну так и сделали бы генератор в обычном смысле (функция (void)->кролик), или систему разных генераторов, и вокруг этого генератора - класс magicians_hat имитирующий корутину и хранящий генератор + копию возвращаемого им значения + ссылки на внутренние переменные генератора если они есть. Чтобы разделить где собственно функционал генератора, и где то что хранится во фрейме корутины.
Иначе возникает много вопросов вида в чём вообще смысл наследоваться от базового класса с парой полей и пустым функционалом, хранить возвращаемого кролика, иметь генератор типа void().
struct Rabbit {};
const int STATE_DONE = 0;
template<class T>
concept RabbitGenerator
= requires(T &t) {
{ t.gen_rabbit() } -> std::same_as<std::optional<Rabbit>>;
{ t.state() } -> std::same_as<int&>;
};
template<RabbitGenerator G>
class magicians_hat {
public:
magicians_hat(G&& generator):
generator_(generator) {}
void pullout_rabbit() {
if (generator_.state() == STATE_DONE) return;
if (auto yielded_rabbit = generator_.gen_rabbit()) {
yielded_rabbit_ = yielded_rabbit.value();
}
}
// интерфейс coroutine_handle
Rabbit& promise() { return yielded_rabbit_; }
void resume();
void destroy();
//...
private:
G generator_;
Rabbit yielded_rabbit_;
};
Да, теперь точно вижу, что вы не поняли назначение как моего примера. так и вообще, для чего в туториалах даются примеры.
У вас, во-первых, получился не базовый класс для построения самих генераторов, а просто класс для получения значений из генераторов.
Во-вторых - его многословность и детали, не относящиеся к построению стейт-машин никак не ведёт к цели статьи - объяснению внутреннего механизма работы корутин.
В-третьих - раз уж вы решили сделать класс для запуска генераторов "по-современному", то и делайте его правильно - где begin(), end(), где iterator, *iterator, ++iterator?
Да уж, концепты в C++98, сильно :)
Для чего promise() в корутинах, тоже совершенно не так поняли.
Ну и state_ в примере - в стэйт-машинах это не "готов-не готов", а точка перехода внутри функции-генератора, она не может быть ограниченна двумя значениями, в наследниках может быть много точек перехода.
Корутины C++20