基于C++20协程实现带交互终端命令的技术疑问
基于C++20协程实现终端命令交互功能的解决方案
问题1:命令代码的编写位置及co_yield/co_return的使用
实际命令逻辑(如del /p的删除确认流程)需编写在专属协程函数内部(例如del_command,而非通用的make_command)。
- co_yield:用于暂停协程执行,向终端传递所需输入类型(
e_command_input)。每次需要用户输入时,通过co_yield抛出需求,协程暂停,直到终端提供输入后恢复。 - co_return:当命令逻辑执行完成(如用户确认删除或取消操作)时,用
co_return结束协程;无返回值时直接写co_return;。
示例协程函数(模拟del /p命令):
auto del_command(const std::string& pattern) -> OS_Command { std::cout << "确认删除所有" << pattern << "文件吗?(y/n): "; // 暂停协程,告知终端需要单个字符输入,恢复后直接获取输入结果 auto input_result = co_yield OS_Command::e_command_input::SINGLE_CHAR; char confirm = std::get<char>(input_result); if (confirm == 'y' || confirm == 'Y') { std::cout << "正在删除文件..." << std::endl; // 执行实际删除逻辑 } else { std::cout << "操作已取消" << std::endl; } co_return; // 结束协程 }
问题2:传递输入类型给Terminal并获取用户输入
需扩展OS_Command和promise_type,实现输入类型传递与结果回传的双向交互:
步骤1:扩展promise_type
添加输入类型、结果的存储变量,并实现yield_value返回可等待对象,让协程恢复后直接获取输入结果:
#include <variant> class OS_Command { public: enum class e_command_input { SINGLE_CHAR, STRING }; struct promise_type; using t_handle = std::coroutine_handle<promise_type>; // 协程恢复时获取输入结果的可等待对象 struct InputAwaiter { promise_type& m_promise; bool await_ready() const noexcept { return false; } // 立即暂停 void await_suspend(t_handle) const noexcept {} // 无需额外暂停处理 auto await_resume() const noexcept { // 恢复时返回输入结果 return m_promise.m_input_result; } }; struct promise_type { auto get_return_object() -> OS_Command { return OS_Command{ t_handle::from_promise(*this) }; } auto initial_suspend() -> std::suspend_always { return {}; } auto final_suspend() noexcept -> std::suspend_always { return {}; } void return_void() {} void unhandled_exception() { m_except = std::current_exception(); } // 存储当前需要的输入类型 e_command_input m_required_input{}; // 存储终端返回的输入结果(支持字符/字符串) std::variant<char, std::string> m_input_result{}; std::exception_ptr m_except{}; // 处理co_yield:保存输入类型并返回可等待对象 auto yield_value(e_command_input input_type) -> InputAwaiter { m_required_input = input_type; return InputAwaiter{*this}; } }; // ... 原有OS_Command构造、析构、start方法 ... // 供终端获取当前所需输入类型 e_command_input get_required_input() const { return m_handle.promise().m_required_input; } // 向协程传递单个字符输入 void set_char_input(char c) { m_handle.promise().m_input_result = c; } // 向协程传递字符串输入 void set_string_input(const std::string& s) { m_handle.promise().m_input_result = s; } // 检查协程是否已完成 bool is_done() const { return m_handle.done(); } private: t_handle m_handle; };
步骤2:终端驱动协程的逻辑
在终端主循环中控制协程执行流程:
int main() { std::cout << "main: Before command" << std::endl; auto cmd = del_command("*.bak"); cmd.start(); // 驱动协程直到完成 while (!cmd.is_done()) { auto input_type = cmd.get_required_input(); if (input_type == OS_Command::e_command_input::SINGLE_CHAR) { char c; std::cin >> c; cmd.set_char_input(c); } else if (input_type == OS_Command::e_command_input::STRING) { std::string s; std::cin >> s; cmd.set_string_input(s); } // 恢复协程执行 cmd.m_handle.resume(); } std::cout << "main: After command" << std::endl; return 0; }
问题3:make_command是否为真正的协程?它是工厂函数吗?
- 是真正的协程:只要函数体内包含
co_await、co_yield或co_return任意一种,就会被编译器识别为C++20协程。你的make_command包含co_await std::suspend_never{};,因此属于协程。 - 是工厂函数:它的作用是创建并返回协程句柄的包装器(
OS_Command对象),让调用方可以控制协程的启动、暂停与恢复,符合工厂函数的定义。但更合理的做法是为每个命令编写专属协程工厂函数(如del_command、rename_command),而非通用的make_command。
内容的提问来源于stack exchange,提问作者darknight
相关产品推荐
相关产品推荐

