Pull to refresh
32
0

User

Send message

возможность просто запустить корутину как обычную функцию и забыть про нее

Хм, как же её тогда терминейтить? Кому-то нужно иметь референс на таск, чтобы вызвать job.terminate(), как у вас в мейне, в примере?

spawn как в boost

это не меняет, наверное, модель того что таска овнит корутину. В случае spawn-а таску (соответственно и корутину) будут овнить внутренности экзекютора в бусте/азио/либо аналог кастомной реализации.

Экономит один malloc (и то не всегда, где-то же вы список корутин будете хранить)

Ну, не совсем. Можно иметь интрузивный список корутин, которые вас интересуют.

откровенно сложнее

но, на практике, имея как идею "останавливать все корутины при каком-то событии", вам нужен список всех таких корутин, чтобы вызвать 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

that is, flush() is called on the tied stream before any input/output operation on *this

std::thread t([&lamp]() { 
   char c;
   while (std::cin >> c) { 
       lamp.change_mode(); 
    } 
});

Хммм, мне кажется здесь дата рейс ? std::cin связан с std::cout. Нужно делать что-то типа cin.tie(nullptr), наверное.

Мне, к примеру, понравился цикл в расте с паттерн матчингом: код.
На плюсах вышло как-то так: код.

    match_opcode(opcode
        , []() { assert(false && "Unknown opcode."); }
        , code(0x0, 0x0, 0xE, 0x0, [&]()
        { /*CLS*/
            clear_display(); })
        , code(0x0, 0x0, 0xE, 0xE, [&]()
        { /*RET*/
            assert(sp_ > 0);
            --sp_;
            assert(sp_ < std::size(stack_));
            pc_ = stack_[sp_]; })
        , code(0x0, _n, _n, _n, [](std::uint16_t)
        { /* SYS addr. This instruction is only used on the old computers
            on which Chip-8 was originally implemented.
            It is ignored by modern interpreters. */ })
        , code(0x1, _n, _n, _n, [&](std::uint16_t nnn)
        { /* JP addr. */
            pc_ = nnn; })
        , code(0x2, _n, _n, _n, [&](std::uint16_t nnn)
        { /* CALL addr. */
            assert(sp_ < std::size(stack_));
            stack_[sp_] = pc_;
            ++sp_;
            pc_ = nnn; })
        , code(0x3, _x, _k, _k, [&](std::uint8_t x, std::uint8_t kk)
        { /* SE Vx, byte. */
            pc_ += ((V_[x] == kk) ? 2 : 0); })


Что в плане компактности и «декларативности» мне очень нравится.
Корутины в C++20 асимметричные [...]

Симметричные. Вроде как в последний момент подправили:
C++ Coroutines: Understanding Symmetric Transfer
Symmetric transfer and no-op coroutines

Отталкиваясь от примера, не вижу где здесь делегаты. Мне кажется, вы смешиваете несколько проблем в одну. Если вам просто нужно превратить массив any в tuple<>, так и сделайте. Имея такую основу, всё становится проще. Вот несколько строчек:


#include <vector>
#include <any>
#include <tuple>

template<typename... Args>
struct Binder
{
    static std::tuple<Args...> convert_args(std::vector<std::any> any_args)
    {
        auto convert = [i = 0u, &any_args](auto& arg) mutable
        {
            arg = std::any_cast<decltype(arg)>(any_args.at(i));
            ++i;
        };
        std::tuple<Args...> vs;
        std::apply([&](Args& ... args) { (convert(args), ...); }, vs);
        return vs;
    }

    template<typename F>
    decltype(auto) call(F f, std::vector<std::any> args) const
    {
        return std::apply(std::move(f), convert_args(std::move(args)));
    }
};

#include <string>
#include <iostream>

int f(int a, std::string s)
{
    std::cout << "int: " << a << "\nstring: " << s << std::endl;
    return 1;
}

int main()
{
    return Binder<int, std::string>().call(
        f, {5, std::string("Hello, Delegates!")});
}

Ну зачем же вы вредный код показываете: virtual деструктор здесь не нужен. Да и вообще, реюзабельность таких темплейтных синглтон классов — сильно под сомнением — вам всё равно нужно еще дописать MyClass. Этот код компилируется [1]:


MyClass instances[100];

[1] https://wandbox.org/permlink/ede7bBfl0KSq6QV2

В плюсах нет возможности в рантайме посмотреть все ф-и с таким-то атрибутом. Даже предлагаемые пропозалы, по-моему, по статической рефлексии не включают в себя возможность в компайл тайме узнать атрибуты ф-и. Мне кажется, атрибуты также не влияют на name mangling, то есть по символам ф-й также не получится что-либо узнать.
Без сторонней тулзы, которая парсит код, не получится. Или я что-то упустил?

Если подумать, я представляю только один случай когда это действительно нужно: когда инициализацию подобьектов нужно выполнить "всю за один раз". То есть по сути, конструкторы базовых классов пустые, а инициализация всего происходит в init(). Но, опять же, мне кажется это больше проблема дизайна класса и/или API. Даже с примером выше — все красиво можно передать в качестве аргументов конструктора базового класса, если ему нужны какие-то дополнительные данные.
Я могу ошибаться, но не представляю случая где двухэтапной инициализации нельзя избежать.
Может у вас есть конкретный пример ?

В том контексте что привели вы, init() после new B() приведет к вызову все того-же B::init(), то есть всю инициализацию можно сделать в конструкторе B. Как по мне, так двухфазная инициализация — усложняет код и лучше ее избегать. То что я видел:


  1. Использование init() чтобы обработать ошибки создания объекта в случае когда эксепшины отключены.
    Иметь is_valid() — это хорошая альтернатива.
  2. Использование init() для настройки "зависимостей" объекта в том случае когда существуют циклические и другие проблемные зависимости объектов.
    Тут, к сожалению, нужно править код, если можно.

Как по мне, всё равно — сомнительно: в случае активного эксепшина, вы все равно пишите в лог, то есть оригинальный эксепшн — теряется. Если на нем построена какая-то логика приложения (восстановки, повторов, прочее) — это логика также не будет выполнена. Если это нормально — то есть не делать какую-то обработку такого-то эксепшина — то почему-бы его и не выбрасывать совсем? Мне кажется, что у вас это имеет смысл из-за второй части статьи: все эксепшины — собираются вместе, ничего не игнорируется и потом все обрабатывается. Но, опять-же, при таком подходе — все деструкторы noexcept, то есть все что сказано в первой части статьи — не имеет смысла.


По поводу:


Когда очень-очень хочется выбросить сразу несколько исключений

мне кажется или вы говорите об std::throw_with_nested и семействе [1] ?


[1] nested_exception: https://en.cppreference.com/w/cpp/error/nested_exception

От себя добавлю, что, всё же, в вашем примере, plain C-версия тоже может быть аккуратненькой:


arr[10];
std::sort(std::begin(arr), std::end(arr));

До 17х плюсов, единственный момент, где удобно использовать C-шные массивы — так это при объявлении "константных массивов", когда можно просто добавлять элементы в конец и не париться про размер, например:


const struct
{
    const int id;
    const char* const name;
} k_info[]
{
    {1, "id1"},
    {2, "id1"},
    {3, "id1"},
    {4, "id1"},
};

Позже, чтобы добавить новый элемент в такой массив, я просто иду и добавляю строчку в конец, не меняя в другом месте (пару строчек выше) размер массива в случае с std::array + с ним не получится использовать анонимную структуру. Но это мелочи.


После 17тых плюсов, использовать std::array для таких случаев стало немного удобней:


std::array data = {1, 2, 3, 4};

работает на ура благодаря class template argument deduction

Почему у вас все функции константные, даже те, которые меняют логическое состояние ?)
я имел в виду то, что написал mickvav ниже
+ логика странная (зачем открывать файл и сразу же его закрывать) + ф-я возвращала bool, судя по всему, но потом была переделка на исключения. В общем, пример неудачен да ещё и с ошибками

Круто. Спасибо за статью.
Для тех, кто хочет посмотреть/подебажить всё вместе — держите CMakeLists.txt:


CMakeLists.txt
cmake_minimum_required(VERSION 3.4)

project(nrcpp)

# Remove unneeded configurations
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267 /wd4244 /wd4996 /wd4018 /wd4477")

file(GLOB kpp_src "KPP_1.1/*.cpp")
file(GLOB kpp_hds "KPP_1.1/*.h")
set(kpp_other_files 
    KPP_1.1/in.txt
    KPP_1.1/in.txt.comment
    KPP_1.1/in.txt.slash
    KPP_1.1/in.txt.trig
    KPP_1.1/limits.h.comment
    KPP_1.1/limits.h.slash
    KPP_1.1/limits.h.trig
    KPP_1.1/out.txt)

add_library(nrcpp_lib nrcpp/LIB/NRC.H nrcpp/LIB/NRC.CPP)
target_include_directories(nrcpp_lib PUBLIC nrcpp/LIB)

add_executable(KPP ${kpp_src} ${kpp_hds} ${kpp_other_files})

file(GLOB nrcpp_src "nrcpp/*.cpp")
file(GLOB nrcpp_hds "nrcpp/*.h")
set(nrcpp_other_files 
    nrcpp/in.txt
    nrcpp/out.txt)

add_executable(nrcpp ${nrcpp_src} ${nrcpp_hds} ${nrcpp_other_files})
target_link_libraries(nrcpp nrcpp_lib)

я собирал как-то так:


mkdir build && ^
cd build && ^
cmake -G "Visual Studio 15 2017 Win64" .. && ^
cmake --build . --config Debug

Отторжения нет. Мне уже нравится ^_^

1
23 ...

Information

Rating
Does not participate
Location
Киев, Киевская обл., Украина
Registered
Activity