возможность просто запустить корутину как обычную функцию и забыть про нее
Хм, как же её тогда терминейтить? Кому-то нужно иметь референс на таск, чтобы вызвать job.terminate(), как у вас в мейне, в примере?
spawn как в boost
это не меняет, наверное, модель того что таска овнит корутину. В случае spawn-а таску (соответственно и корутину) будут овнить внутренности экзекютора в бусте/азио/либо аналог кастомной реализации.
Экономит один malloc (и то не всегда, где-то же вы список корутин будете хранить)
Ну, не совсем. Можно иметь интрузивный список корутин, которые вас интересуют.
откровенно сложнее
но, на практике, имея как идею "останавливать все корутины при каком-то событии", вам нужен список всех таких корутин, чтобы вызвать job.terminate(), не так ли ?
В целом, мне кажется, первый вариант с тем что таска овнит корутину - был бы проще, конкретно для вашего примера в статье. И убрал бы с интернета ещё один пример использования shared_ptr ^_^
Мне кажется, использование std::shared_ptr здесь лишнее. Вы либо овните корутину в task-е (мануально вызывая .destroy() + suspend_always из final_suspend()) либо "запоминаете все текущие корутины которые активны и которые можно и нужно терминейтить". Это тоже можно сделать пробросив ссылку на что-то что запоминает и регистрируя/удаляя корутину когда случается initial_suspend/final_suspend.
Отталкиваясь от примера, не вижу где здесь делегаты. Мне кажется, вы смешиваете несколько проблем в одну. Если вам просто нужно превратить массив any в tuple<>, так и сделайте. Имея такую основу, всё становится проще. Вот несколько строчек:
Ну зачем же вы вредный код показываете: virtual деструктор здесь не нужен. Да и вообще, реюзабельность таких темплейтных синглтон классов — сильно под сомнением — вам всё равно нужно еще дописать MyClass. Этот код компилируется [1]:
В плюсах нет возможности в рантайме посмотреть все ф-и с таким-то атрибутом. Даже предлагаемые пропозалы, по-моему, по статической рефлексии не включают в себя возможность в компайл тайме узнать атрибуты ф-и. Мне кажется, атрибуты также не влияют на name mangling, то есть по символам ф-й также не получится что-либо узнать.
Без сторонней тулзы, которая парсит код, не получится. Или я что-то упустил?
Если подумать, я представляю только один случай когда это действительно нужно: когда инициализацию подобьектов нужно выполнить "всю за один раз". То есть по сути, конструкторы базовых классов пустые, а инициализация всего происходит в init(). Но, опять же, мне кажется это больше проблема дизайна класса и/или API. Даже с примером выше — все красиво можно передать в качестве аргументов конструктора базового класса, если ему нужны какие-то дополнительные данные.
Я могу ошибаться, но не представляю случая где двухэтапной инициализации нельзя избежать.
Может у вас есть конкретный пример ?
В том контексте что привели вы, init() после new B() приведет к вызову все того-же B::init(), то есть всю инициализацию можно сделать в конструкторе B. Как по мне, так двухфазная инициализация — усложняет код и лучше ее избегать. То что я видел:
Использование init() чтобы обработать ошибки создания объекта в случае когда эксепшины отключены.
Иметь is_valid() — это хорошая альтернатива.
Использование init() для настройки "зависимостей" объекта в том случае когда существуют циклические и другие проблемные зависимости объектов.
Тут, к сожалению, нужно править код, если можно.
Как по мне, всё равно — сомнительно: в случае активного эксепшина, вы все равно пишите в лог, то есть оригинальный эксепшн — теряется. Если на нем построена какая-то логика приложения (восстановки, повторов, прочее) — это логика также не будет выполнена. Если это нормально — то есть не делать какую-то обработку такого-то эксепшина — то почему-бы его и не выбрасывать совсем? Мне кажется, что у вас это имеет смысл из-за второй части статьи: все эксепшины — собираются вместе, ничего не игнорируется и потом все обрабатывается. Но, опять-же, при таком подходе — все деструкторы noexcept, то есть все что сказано в первой части статьи — не имеет смысла.
По поводу:
Когда очень-очень хочется выбросить сразу несколько исключений
мне кажется или вы говорите об std::throw_with_nested и семействе [1] ?
До 17х плюсов, единственный момент, где удобно использовать C-шные массивы — так это при объявлении "константных массивов", когда можно просто добавлять элементы в конец и не париться про размер, например:
Позже, чтобы добавить новый элемент в такой массив, я просто иду и добавляю строчку в конец, не меняя в другом месте (пару строчек выше) размер массива в случае с std::array + с ним не получится использовать анонимную структуру. Но это мелочи.
После 17тых плюсов, использовать std::array для таких случаев стало немного удобней:
+ логика странная (зачем открывать файл и сразу же его закрывать) + ф-я возвращала bool, судя по всему, но потом была переделка на исключения. В общем, пример неудачен да ещё и с ошибками
Хм, как же её тогда терминейтить? Кому-то нужно иметь референс на таск, чтобы вызвать job.terminate(), как у вас в мейне, в примере?
это не меняет, наверное, модель того что таска овнит корутину. В случае spawn-а таску (соответственно и корутину) будут овнить внутренности экзекютора в бусте/азио/либо аналог кастомной реализации.
Ну, не совсем. Можно иметь интрузивный список корутин, которые вас интересуют.
но, на практике, имея как идею "останавливать все корутины при каком-то событии", вам нужен список всех таких корутин, чтобы вызвать job.terminate(), не так ли ?
В целом, мне кажется, первый вариант с тем что таска овнит корутину - был бы проще, конкретно для вашего примера в статье. И убрал бы с интернета ещё один пример использования shared_ptr ^_^
Мне кажется, использование std::shared_ptr здесь лишнее. Вы либо овните корутину в task-е (мануально вызывая .destroy() + suspend_always из final_suspend()) либо "запоминаете все текущие корутины которые активны и которые можно и нужно терминейтить". Это тоже можно сделать пробросив ссылку на что-то что запоминает и регистрируя/удаляя корутину когда случается initial_suspend/final_suspend.
Охх, т.е., sync_with_stdio(true) делает больше чем "синхронизирует standard C streams". Спасибо.
Я так понимаю std::cin вызывает flush для std::cout - https://en.cppreference.com/w/cpp/io/basic_ios/tie
Хммм, мне кажется здесь дата рейс ? std::cin связан с std::cout. Нужно делать что-то типа cin.tie(nullptr), наверное.
На плюсах вышло как-то так: код.
Что в плане компактности и «декларативности» мне очень нравится.
Симметричные. Вроде как в последний момент подправили:
C++ Coroutines: Understanding Symmetric Transfer
Symmetric transfer and no-op coroutines
Отталкиваясь от примера, не вижу где здесь делегаты. Мне кажется, вы смешиваете несколько проблем в одну. Если вам просто нужно превратить массив
any
вtuple<>
, так и сделайте. Имея такую основу, всё становится проще. Вот несколько строчек:Ну зачем же вы вредный код показываете: virtual деструктор здесь не нужен. Да и вообще, реюзабельность таких темплейтных синглтон классов — сильно под сомнением — вам всё равно нужно еще дописать
MyClass
. Этот код компилируется [1]:[1] https://wandbox.org/permlink/ede7bBfl0KSq6QV2
Без сторонней тулзы, которая парсит код, не получится. Или я что-то упустил?
Если подумать, я представляю только один случай когда это действительно нужно: когда инициализацию подобьектов нужно выполнить "всю за один раз". То есть по сути, конструкторы базовых классов пустые, а инициализация всего происходит в
init()
. Но, опять же, мне кажется это больше проблема дизайна класса и/или API. Даже с примером выше — все красиво можно передать в качестве аргументов конструктора базового класса, если ему нужны какие-то дополнительные данные.Я могу ошибаться, но не представляю случая где двухэтапной инициализации нельзя избежать.
Может у вас есть конкретный пример ?
В том контексте что привели вы,
init()
послеnew B()
приведет к вызову все того-жеB::init()
, то есть всю инициализацию можно сделать в конструктореB
. Как по мне, так двухфазная инициализация — усложняет код и лучше ее избегать. То что я видел:init()
чтобы обработать ошибки создания объекта в случае когда эксепшины отключены.Иметь
is_valid()
— это хорошая альтернатива.init()
для настройки "зависимостей" объекта в том случае когда существуют циклические и другие проблемные зависимости объектов.Тут, к сожалению, нужно править код, если можно.
Как по мне, всё равно — сомнительно: в случае активного эксепшина, вы все равно пишите в лог, то есть оригинальный эксепшн — теряется. Если на нем построена какая-то логика приложения (восстановки, повторов, прочее) — это логика также не будет выполнена. Если это нормально — то есть не делать какую-то обработку такого-то эксепшина — то почему-бы его и не выбрасывать совсем? Мне кажется, что у вас это имеет смысл из-за второй части статьи: все эксепшины — собираются вместе, ничего не игнорируется и потом все обрабатывается. Но, опять-же, при таком подходе — все деструкторы
noexcept
, то есть все что сказано в первой части статьи — не имеет смысла.По поводу:
мне кажется или вы говорите об
std::throw_with_nested
и семействе [1] ?[1] nested_exception: https://en.cppreference.com/w/cpp/error/nested_exception
Статью не читал, у вас тут память течёт:
От себя добавлю, что, всё же, в вашем примере, plain C-версия тоже может быть аккуратненькой:
До 17х плюсов, единственный момент, где удобно использовать C-шные массивы — так это при объявлении "константных массивов", когда можно просто добавлять элементы в конец и не париться про размер, например:
Позже, чтобы добавить новый элемент в такой массив, я просто иду и добавляю строчку в конец, не меняя в другом месте (пару строчек выше) размер массива в случае с
std::array
+ с ним не получится использовать анонимную структуру. Но это мелочи.После 17тых плюсов, использовать
std::array
для таких случаев стало немного удобней:работает на ура благодаря class template argument deduction
Круто. Спасибо за статью.
Для тех, кто хочет посмотреть/подебажить всё вместе — держите CMakeLists.txt:
я собирал как-то так:
Отторжения нет. Мне уже нравится ^_^