如何设计一个"Awaitable" base class?
How to design an "Awaitable" base class?
许多地方的 C++ 标准库提供了一种“等待”API:例如std::future
和 std::condition_variable
可以立即“尝试”获取它们的值,无限期地“等待”它们的值,“wait_for”某个 std::chrono::duration
,或“[=46=” ]”达到某个std::chrono::time_point
。我正在努力创建一个抽象基础 class 来捕获这些相同的操作。
template <typename T>
class awaitable {
public:
virtual std::optional<T> try() = 0;
virtual std::optional<T> wait() = 0;
template <typename Rep, typename Period>
virtual std::optional<T> wait_for(const std::chrono::duration<Rep, Period>&) = 0;
template <typename Clock, typename Duration>
virtual std::optional<T> wait_until(const std::chrono::time_point<Clock, Duration>&) = 0;
};
try
和 wait
没有问题。 wait_for
和 wait_until
需要模板参数,因此不能是虚拟的。
是否有一种“干净”的方式来定义这样的接口?`
我考虑过的一些选项(除非我遗漏了什么)似乎不可行:
- 使用
std::any
或其他某种类型的擦除。在内部将 duration
对象传递给另一个函数时,我仍然需要知道正确转换它的确切类型。
- 使用访客模式。这将需要我在一个中央位置指定一个硬编码列表,其中包含从“Awaitable”派生的所有类型,引入循环依赖并限制可扩展性。
- 使用
std::duration_cast
和 std::time_point_cast
将任何传入类型转换为 std::chrono::nanoseconds
或 std::chrono::time_point<std::chrono::high_resolution_clock>
,因此会有一个非虚拟模板化方法和一个虚拟非模板方法。这似乎会引入不必要的开销和潜在的不当行为,因为我不确定是否保证每种可能的传入类型都可以转换为那些常见类型。
到目前为止,第三种变体似乎是我唯一的选择,而且不是很好。
我认为您的第三种方法可能是可行的方法。有疑问,您必须努力限制可能的误用。
你也可以看看非循环访问者模式:
https://en.wikipedia.org/wiki/Visitor_pattern
Acyclic Visitor C++
对于某些场景,Andrei Alexandrescu(真的可以推荐他的书)提供的实现方式对我帮助很大。它需要一些努力才能完全理解,有点侵入性,据我所知,至少对于 C++ < 14,不可能在这里保持 100% 无宏,但它具有您可能需要的去中心化的巨大优势。此外,它的次要 dynamic_cast-usage(未被误用作动态开关)对于大多数用例和现代架构来说并不是真正的问题。
许多地方的 C++ 标准库提供了一种“等待”API:例如std::future
和 std::condition_variable
可以立即“尝试”获取它们的值,无限期地“等待”它们的值,“wait_for”某个 std::chrono::duration
,或“[=46=” ]”达到某个std::chrono::time_point
。我正在努力创建一个抽象基础 class 来捕获这些相同的操作。
template <typename T>
class awaitable {
public:
virtual std::optional<T> try() = 0;
virtual std::optional<T> wait() = 0;
template <typename Rep, typename Period>
virtual std::optional<T> wait_for(const std::chrono::duration<Rep, Period>&) = 0;
template <typename Clock, typename Duration>
virtual std::optional<T> wait_until(const std::chrono::time_point<Clock, Duration>&) = 0;
};
try
和 wait
没有问题。 wait_for
和 wait_until
需要模板参数,因此不能是虚拟的。
是否有一种“干净”的方式来定义这样的接口?`
我考虑过的一些选项(除非我遗漏了什么)似乎不可行:
- 使用
std::any
或其他某种类型的擦除。在内部将duration
对象传递给另一个函数时,我仍然需要知道正确转换它的确切类型。 - 使用访客模式。这将需要我在一个中央位置指定一个硬编码列表,其中包含从“Awaitable”派生的所有类型,引入循环依赖并限制可扩展性。
- 使用
std::duration_cast
和std::time_point_cast
将任何传入类型转换为std::chrono::nanoseconds
或std::chrono::time_point<std::chrono::high_resolution_clock>
,因此会有一个非虚拟模板化方法和一个虚拟非模板方法。这似乎会引入不必要的开销和潜在的不当行为,因为我不确定是否保证每种可能的传入类型都可以转换为那些常见类型。
到目前为止,第三种变体似乎是我唯一的选择,而且不是很好。
我认为您的第三种方法可能是可行的方法。有疑问,您必须努力限制可能的误用。
你也可以看看非循环访问者模式:
https://en.wikipedia.org/wiki/Visitor_pattern
Acyclic Visitor C++
对于某些场景,Andrei Alexandrescu(真的可以推荐他的书)提供的实现方式对我帮助很大。它需要一些努力才能完全理解,有点侵入性,据我所知,至少对于 C++ < 14,不可能在这里保持 100% 无宏,但它具有您可能需要的去中心化的巨大优势。此外,它的次要 dynamic_cast-usage(未被误用作动态开关)对于大多数用例和现代架构来说并不是真正的问题。