【发布时间】:2018-04-27 23:16:05
【问题描述】:
我正在尝试实现一个通用的对象输入流。也就是说,实现的接口或轻量级代理。实现的细节是未知的,即我的库的用户可以编写自己的 protobuf 消息流,将其传递给我的库并返回,例如字符串流或任何其他流。我想保持流的通用接口,以便用户可以编写自己的转换并构建转换管道。
流的接口应该是这样的:
template <typename T>
class Stream {
public:
T* input();
}
在每次调用时,input() 应返回流中的下一个对象,如果流为空,则返回空指针。
问题是如果T* 可转换为U*,我希望Stream<T> 可转换为Stream<U>。
我不成功的尝试是像这样使用指向实现的指针:
class StreamImplBase {
public:
virtual void* input_raw() = 0;
}
template <typename T>
class StreamImpl: public StreamImplBase {
public:
void* input_raw() final { return input(); }
virtual T* input() = 0;
}
template <typename T>
class Stream {
StreamImplBase* impl;
public:
Stream(StreamImpl<T>* impl): impl(impl) {}
T* input() { return static_cast<T*>(impl->input_raw()); }
}
StreamImpl<T> 的构造函数保证从input_raw() 返回的void* 是通过将T 转换为void* 获得的,因此static_cast<T*> 是安全的。
但是,如果我执行任何转换,则此陈述将不正确。也就是说,从StreamImpl<U> 构建Stream<T> 是不安全的,即使U* 可以转换为T*。
所以我的问题是,我该如何处理这个问题?
我看到了下一个可能性:
在流中存储一个转换器(例如
std::function<T*(void*)>)并在每次转换时更新它。这似乎不必要地昂贵;存储
static_cast<U*>((T*)0)的结果并将这个结果添加到从input_raw()获得的指针上。这似乎是不必要的危险;添加第二个模板参数
OrigT并存储StreamImpl<OrigT>*而不是存储StreamImplBase*。这将限制该类的可能应用,我想避免这种情况;使用
dynamic_cast不是一种选择,因为不能从void*使用dynamic_cast。
还有其他可能吗?其他人如何实现这样的东西?
这是一个用例。假设我们有一个 protobuf 消息X。我希望这个工作:
Stream<X> stream = ...;
Stream<google::protobuf::Message> raw_stream = stream;
同样,我不知道Stream<X> 是如何实现的。我只知道它包含一个共享指针,指向生成消息的某个实现。
【问题讨论】:
-
@Justin 我已经更新了我的问题。
-
您不能在需要
Stream<U>*/Stream<U>&的地方使用Stream<T>*指针或Stream<T>&引用。只有当Stream<U>对象是从Stream<T>对象构造时,您想要做的事情才有意义。在这种情况下,您只需为Stream<U>提供一组额外的构造函数/赋值运算符来处理T/Stream<T>作为输入。但我认为这不是你要找的。如果你能提供一个minimal reproducible example 来展示你想要如何使用Stream会有所帮助,但我怀疑答案将是“它不会那样工作”。 -
@RemyLebeau 我已经用一个例子更新了我的问题。在这个例子中,我假设
Stream使用 pimpl,这样我就可以在不转换繁重实现的情况下转换流。 -
我刚刚意识到有一个非常直接的解决方案:创建一个流实现来进行转换并将其链接到原始实现。我会说我想避免链接实现,因为它可能会很慢,但是您可能会反对我已经有两个虚拟调用并且再添加两个并不是什么大问题。
-
@Zelta w-- 是的,你刚刚写了我刚刚写的东西。您无法使用泛型类型执行向下转换,这意味着它的唯一工作方式是让用户能够将假设 X 的流转换为任何假设 Y 的流,这不是泛型可以做的事情,即泛型类型派生。所以唯一可行的解决方案是一个通用的转换器对象,它通过管道将一种类型的流解释为其他类型的流。