강좌

C++ 코루틴(coroutine) 활용 강좌: #4 : co_yield

하늘흐늘 2022. 5. 24. 14:49
반응형

이번 강좌는 co_yield 이용하여 값을 생산하면서 제어권을 다른 쪽으로 넘기는 예제를 살펴보도록 하겠습니다. 코루틴 상태 관련 객체로 cotask_enumarator을 정의하여 사용합니다. 구체적으로 값을 생산하는 함수A 예를 살펴보도록 하겠습니다. 이 예제는 Unity 등 C#에서 많이 쓰는 IEnumerator를 리턴하는 함수와 같은 cotask_func_enum 함수를 정의하고 있습니다. 이 예제는 C#에서 많이 쓰는 코루틴 기법을 C++에서 사용하는 법을 보여주고 있습니다.

예제 코드는 아래와 같습니다.

#include <iostream>
#include <coroutine>
#include <concepts>
#include <exception>
#include <optional>
#include <vector>


using namespace std;

template<typename T>
struct cotask_enumarator {
	struct promise_type;
	using handle_type = std::coroutine_handle<promise_type>;

	struct promise_type {
		std::optional<T> value_ = std::nullopt;
		std::exception_ptr exception_;

		cotask_enumarator get_return_object() {
			return cotask_enumarator(handle_type::from_promise(*this));
		}
		std::suspend_always initial_suspend() { return {}; }
		std::suspend_always final_suspend() noexcept { return {}; }
		void unhandled_exception() { exception_ = std::current_exception(); }

		template<std::convertible_to<T> From>
		std::suspend_always yield_value(From&& from) {
			value_ = std::forward<From>(from);
			return {};
		}

		void return_void() {}
	};

	handle_type handler_;

	cotask_enumarator(handle_type handler) : handler_(handler) {}
	~cotask_enumarator()
	{
		handler_.destroy();
	}

	bool done() {
		return handler_.done();
	}

	std::optional<T> operator()() {
		handler_();
		if (handler_.promise().exception_)
			std::rethrow_exception(handler_.promise().exception_);

		auto ret = std::move(handler_.promise().value_);
		handler_.promise().value_ = std::nullopt;

		return ret;
	}
};

cotask_enumarator<int> cotask_func_enum()
{
	vector<int> items{ 1,2,3,4,5 };

	for (auto item : items)
	{
		co_yield item;
	}
}

int main(int argc, char* argv[])
{
	auto cotask = cotask_func_enum();

	while (!cotask.done())
	{
		auto value = cotask();
		if (value.has_value())
		{
			cout << value.value() << endl;
		}
	}



	return 0;
}

실행 결과는 아래와 같습니다.

1
2
3
4
5

 

예제 설명

cotask_func_enum() 함수에서 코루틴으로 값을 생산하고 호출한 쪽에서는 생산한 값을 소비하고 있습니다. Unity등 C#에서는 정말로 많이 쓰이는 코루틴 기법입니다.


cotask_enumarator

co_yield를 처리하기 위하여 cotask_enumarator 클래스를 정의하였습니다. 해당 클래스는 co_yield로 생산된 값을 받아서 값을 호출한 쪽에 리턴하는 역활을 합니다. co_return과 같은 경우 void로 처리하고 있습니다. C# 등에서 보통 이런 식으로 처리하기 때문에 co_return에서 값을 받지 않도록 만들었습니다. 만약 co_yield로 값을 생성하는 것 이외에 co_return로 값을 리턴하는 식으로 처리하기를 원한다면 클래스의 void return_void() {} 부분을 이전 예제의 cotask_return클래스의 return처리 부분처럼 수정하여 주시면 됩니다.

co_yield

co_yield는 값을 생산하고 실행을 중간에 멈춥니다. 값을 리턴하고 실행을 멈추고 제어권을 넘기는 역활을 하는데 co_return과 틀린 점은 코루틴 함수 실행을 종료하지는 않습니다.

 

반응형