Ответ 1
N4775 описывает предложение сопрограмм для C++ 20. Он вводит ряд разных идей. Следующее из моего блога на https://dwcomputersolutions.net. Больше информации можно найти в других моих постах.
Прежде чем мы рассмотрим всю программу сопрограмм Hello World, пройдите различные детали шаг за шагом. К ним относятся:
- Обещание сопрограммы
- Контекст сопрограммы
- Будущее сопрограммы
- Ручка сопрограммы
- Сама сопрограмма
- Подпрограмма, которая фактически использует сопрограмму
Весь файл включен в конце этого пост.
Сопрограмма
Future f()
{
co_return 42;
}
Мы создаем нашу сопрограмму с помощью
Future myFuture = f();
Это простая сопрограмма, которая просто возвращает значение 42
. Это сопрограмма
потому что он включает в себя ключевое слово co_return
. Любая функция, которая имеет ключевые слова
co_await
, co_return
или co_yield
- сопрограмма.
Первое, что вы заметите, это то, что, хотя мы возвращаем целое число, тип возврата сопрограммы (определенный пользователем) тип Future. Причина в том, что когда мы вызываем нашу сопрограмму, мы не запускаем функцию прямо сейчас, скорее мы инициализировать объект, который в конечном итоге даст нам значение, которое мы ищем Ака наше будущее.
Нахождение обещанного типа
Когда мы создаем нашу сопрограмму, первое, что делает компилятор, - это находит Тип обещания, который представляет этот конкретный тип сопрограммы.
Мы сообщаем компилятору, какой тип обещания принадлежит какой функции сопрограммы подпись путем создания шаблона частичной специализации для
template <typename R, typename P...>
struct coroutine_trait
{};
with a member called 'promise_type' that defines our Promise Type
В нашем примере мы могли бы использовать что-то вроде:
template<>
struct std::experimental::coroutines_v1::coroutine_traits<Future> {
using promise_type = Promise;
};
Здесь мы создаем специализацию coroutine_trait
без указания параметров и
тип возврата Future
, он точно соответствует сигнатуре нашей функции сопрограммы
Future f(void)
. promise_type
- это тип обещания, который в нашем случае
struct Promise
.
Теперь мы пользователь, мы обычно не будем создавать свои собственные coroutine_trait
специализация, так как библиотека сопрограмм обеспечивает хороший простой способ
укажите promise_type
в самом классе Future. Подробнее об этом позже.
Контекст сопрограммы
Как упоминалось в моем предыдущем посте, потому что сопрограммы могут быть приостановлены и возобновляемые локальные переменные не всегда могут быть сохранены в стеке. Хранить нестабильные локальные переменные, компилятор будет размещать объект Context на куча Будет также сохранен экземпляр нашего Обещания.
Обещание, будущее и ручка
Сопрограммы в основном бесполезны, если они не могут общаться с внешний мир. Наше обещание говорит нам, как должна вести себя сопрограмма, в то время как наш Будущий объект позволяет другому коду взаимодействовать с сопрограммой. Обещание и Будущее затем общаться друг с другом через нашу ручку сопрограммы.
Обещание
Простое сопрограммное обещание выглядит примерно так:
struct Promise
{
Promise() : val (-1), done (false) {}
std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
std::experimental::coroutines_v1::suspend_always final_suspend() {
this->done = true;
return {};
}
Future get_return_object();
void unhandled_exception() { abort(); }
void return_value(int val) {
this->val = val;
}
int val;
bool done;
};
Future Promise::get_return_object()
{
return Future { Handle::from_promise(*this) };
}
Как уже упоминалось, обещание распределяется, когда сопрограмма создается и выходы на весь срок жизни сопрограммы.
После этого компилятор вызывает get_return_object
Эта пользовательская функция
затем отвечает за создание объекта Future и возвращение его
сопрограммный инициатор.
В нашем случае мы хотим, чтобы наше будущее могло общаться с нашими сопрограммами. поэтому мы создаем наше будущее с ручкой для нашей сопрограммы. Это позволит нашим Будущее, чтобы получить доступ к нашему обещанию.
Как только наша сопрограмма создана, нам нужно знать, хотим ли мы запустить
это немедленно или мы хотим, чтобы это было приостановлено немедленно. Это
сделано путем вызова функции Promise::initial_suspend()
. Эта функция возвращает
Официант, которого мы рассмотрим в другом посте.
В нашем случае, так как мы хотим, чтобы функция запускалась немедленно, мы вызываем
suspend_never
. Если мы приостановили функцию, нам нужно было бы запустить
сопрограммы, вызвав метод возобновления на дескрипторе.
Нам нужно знать, что делать, когда оператор co_return
вызывается в
сопрограмма. Это делается с помощью функции return_value
. В этом случае мы
сохранить значение в Обещании для последующего извлечения через Future.
В случае исключения нам нужно знать, что делать. Это сделано
Функция unhandled_exception
. Поскольку в нашем примере исключения не должны
случается, мы просто прерываем.
Наконец, нам нужно знать, что делать, прежде чем уничтожить нашу сопрограмму. Это
сделано через final_suspend function
В этом случае, так как мы хотим получить
результат, поэтому мы возвращаемся suspend_always
. Сопрограмма должна быть уничтожена
с помощью метода сопрограммы destroy
. В противном случае, если мы вернемся
suspend_never
сопрограмма уничтожает себя, как только заканчивает работу.
Ручка
Ручка дает доступ к сопрограмме, а также его обещание. Есть два ароматы, пустая ручка, когда нам не нужно получить доступ к обещанию и сопрограмма с типом обещания, когда нам нужно получить доступ к обещанию.
template <typename _Promise = void>
class coroutine_handle;
template <>
class coroutine_handle<void> {
public:
void operator()() { resume(); }
//resumes a suspended coroutine
void resume();
//destroys a suspended coroutine
void destroy();
//determines whether the coroutine is finished
bool done() const;
};
template <Promise>
class coroutine_handle : public coroutine_handle<void>
{
//gets the promise from the handle
Promise& promise() const;
//gets the handle from the promise
static coroutine_handle from_promise(Promise& promise) no_except;
};
Будущее
Будущее выглядит так:
class [[nodiscard]] Future
{
public:
explicit Future(Handle handle)
: m_handle (handle)
{}
~Future() {
if (m_handle) {
m_handle.destroy();
}
}
using promise_type = Promise;
int operator()();
private:
Handle m_handle;
};
int Future::operator()()
{
if (m_handle && m_handle.promise().done) {
return m_handle.promise().val;
} else {
return -1;
}
}
Объект Future отвечает за абстрагирование сопрограммы вовне
Мир. У нас есть конструктор, который берет ручку из обещания согласно
обещание реализации get_return_object
.
Деструктор уничтожает сопрограмму, так как в нашем случае это будущее, которое контролировать время обещания.
наконец, у нас есть строка:
using promise_type = Promise;
Библиотека C++ избавляет нас от реализации нашего собственного coroutine_trait
, как мы это сделали
выше, если мы определим наш promise_type
в классе возврата сопрограммы.
И там у нас это есть. Наша самая первая простая сопрограмма.
Полный источник
#include <experimental/coroutine>
#include <iostream>
struct Promise;
class Future;
using Handle = std::experimental::coroutines_v1::coroutine_handle<Promise>;
struct Promise
{
Promise() : val (-1), done (false) {}
std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
std::experimental::coroutines_v1::suspend_always final_suspend() {
this->done = true;
return {};
}
Future get_return_object();
void unhandled_exception() { abort(); }
void return_value(int val) {
this->val = val;
}
int val;
bool done;
};
class [[nodiscard]] Future
{
public:
explicit Future(Handle handle)
: m_handle (handle)
{}
~Future() {
if (m_handle) {
m_handle.destroy();
}
}
using promise_type = Promise;
int operator()();
private:
Handle m_handle;
};
Future Promise::get_return_object()
{
return Future { Handle::from_promise(*this) };
}
int Future::operator()()
{
if (m_handle && m_handle.promise().done) {
return m_handle.promise().val;
} else {
return -1;
}
}
//The Co-routine
Future f()
{
co_return 42;
}
int main()
{
Future myFuture = f();
std::cout << "The value of myFuture is " << myFuture() << std::endl;
return 0;
}
Awaiters
Оператор co_await
позволяет нам приостановить нашу сопрограмму и вернуть контроль
обратно к вызывающему сопрограмму. Это позволяет нам выполнять другую работу в ожидании завершения нашей операции. Когда они закончат, мы можем возобновить их с
именно там, где мы остановились.
Оператор co_await
может обработать выражение несколькими способами.
справа от него. Сейчас мы рассмотрим простейший случай, и именно здесь наш
co_await
выражение возвращает Awaiter.
Ожидатель - это простой struct
или class
, который реализует следующее
методы: await_ready
, await_suspend
и await_resume
.
bool await_ready() const {...}
просто возвращает, готовы ли мы возобновить
сопрограммы или нам нужно посмотреть на приостановку нашей сопрограммы. Если предположить,
await_ready
возвращает ложь. Переходим к запуску await_suspend
Для метода await_suspend
доступно несколько подписей. Самым простым является void
await_suspend(coroutine_handle<> handle) {...}
. Это ручка для
объект сопрограммы, который наш co_await
приостановит. Как только эта функция завершится,
управление возвращается обратно вызывающей стороне объекта сопрограммы. Именно эта функция
который отвечает за хранение ручки сопрограммы на потом, чтобы наши
сопрограмма не может быть приостановлена навсегда.
Однажды handle.resume()
вызывается; await_ready
возвращает ложь; или какой-то другой
механизм возобновляет нашу сопрограмму, вызывается метод auto await_resume()
.
возвращаемое значение из await_resume
- это значение, которое возвращает оператор co_await
.
Иногда для expr в co_await expr
нецелесообразно возвращать ожидающего
как описано выше. Если expr
возвращает класс, класс может предоставить свой собственный
экземпляр Awaiter operator co_await (...) which will return the Awaiter.
Alternatively one can implement an
await_transform method in our
promise_type ', который преобразует
expr в Awaiter.
Теперь, когда мы сняли с Awaiter, я хотел бы отметить, что
Методы initial_suspend
и final_suspend
в нашем promise_type
оба возвращают
Awaiters. Объект suspend_always
и suspend_never
являются тривиальными ожидающими.
suspend_always
возвращает true для await_ready
, а suspend_never
возвращает
ложный. Ничто не мешает вам раскрутить свои собственные.
Если вам интересно, как выглядит настоящий Официант в реальной жизни, взгляните на мой будущий объект. Он хранит ручку сопрограммы в лямде для последующей обработки.