【问题标题】:Implementing Stream<T> which can be converted to Stream<U> where U is base of T实现 Stream<T> 可以转换为 Stream<U> 其中 U 是 T 的基础
【发布时间】:2018-04-27 23:16:05
【问题描述】:

我正在尝试实现一个通用的对象输入流。也就是说,实现的接口或轻量级代理。实现的细节是未知的,即我的库的用户可以编写自己的 protobuf 消息流,将其传递给我的库并返回,例如字符串流或任何其他流。我想保持流的通用接口,以便用户可以编写自己的转换并构建转换管道。

流的接口应该是这样的:

template <typename T>
class Stream {
public:
    T* input();
}

在每次调用时,input() 应返回流中的下一个对象,如果流为空,则返回空指针。

问题是如果T* 可转换为U*,我希望Stream&lt;T&gt; 可转换为Stream&lt;U&gt;

我不成功的尝试是像这样使用指向实现的指针:

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&lt;T&gt; 的构造函数保证从input_raw() 返回的void* 是通过将T 转换为void* 获得的,因此static_cast&lt;T*&gt; 是安全的。

但是,如果我执行任何转换,则此陈述将不正确。也就是说,从StreamImpl&lt;U&gt; 构建Stream&lt;T&gt; 是不安全的,即使U* 可以转换为T*

所以我的问题是,我该如何处理这个问题?

我看到了下一个可能性:

  • 在流中存储一个转换器(例如std::function&lt;T*(void*)&gt;)并在每次转换时更新它。这似乎不必要地昂贵;

  • 存储static_cast&lt;U*&gt;((T*)0)的结果并将这个结果添加到从input_raw()获得的指针上。这似乎是不必要的危险;

  • 添加第二个模板参数OrigT并存储StreamImpl&lt;OrigT&gt;*而不是存储StreamImplBase*。这将限制该类的可能应用,我想避免这种情况;

  • 使用dynamic_cast 不是一种选择,因为不能从void* 使用dynamic_cast

还有其他可能吗?其他人如何实现这样的东西?


这是一个用例。假设我们有一个 protobuf 消息X。我希望这个工作:

Stream<X> stream = ...;
Stream<google::protobuf::Message> raw_stream = stream;

同样,我不知道Stream&lt;X&gt; 是如何实现的。我只知道它包含一个共享指针,指向生成消息的某个实现。

【问题讨论】:

  • @Justin 我已经更新了我的问题。
  • 您不能在需要 Stream&lt;U&gt;*/Stream&lt;U&gt;&amp; 的地方使用 Stream&lt;T&gt;* 指针或 Stream&lt;T&gt;&amp; 引用。只有当 Stream&lt;U&gt; 对象是从 Stream&lt;T&gt; 对象构造时,您想要做的事情才有意义。在这种情况下,您只需为Stream&lt;U&gt; 提供一组额外的构造函数/赋值运算符来处理T/Stream&lt;T&gt; 作为输入。但我认为这不是你要找的。如果你能提供一个minimal reproducible example 来展示你想要如何使用Stream 会有所帮助,但我怀疑答案将是“它不会那样工作”。
  • @RemyLebeau 我已经用一个例子更新了我的问题。在这个例子中,我假设 Stream 使用 pimpl,这样我就可以在不转换繁重实现的情况下转换流。
  • 我刚刚意识到有一个非常直接的解决方案:创建一个流实现来进行转换并将其链接到原始实现。我会说我想避免链接实现,因为它可能会很慢,但是您可能会反对我已经有两个虚拟调用并且再添加两个并不是什么大问题。
  • @Zelta w-- 是的,你刚刚写了我刚刚写的东西。您无法使用泛型类型执行向下转换,这意味着它的唯一工作方式是让用户能够将假设 X 的流转换为任何假设 Y 的流,这不是泛型可以做的事情,即泛型类型派生。所以唯一可行的解​​决方案是一个通用的转换器对象,它通过管道将一种类型的流解释为其他类型的流。

标签: c++ pointers casting


【解决方案1】:

这个:

template <typename T>
class Stream {
public:
  T* input();
};

是一个有一个操作的对象,它接受0个参数并返回一个T*

是这样的:

std::function<T*()>

诚然,您像 stream() 而不是 stream.input() 那样调用它。

使用第二种解决方案,如果UT 的基数,那么您可以将上述转换为std::function&lt;U*()&gt;。这解决了你的问题。

我个人认为在您的信息流名称和() 之间输入.input 不值得做很多工作。

其他人已经完成的类型擦除是最好的类型擦除。

【讨论】:

  • 是的,你说得对!用std::function 替换Stream 是有争议的,但是,我研究了std::function 的libc++ (llvm) 实现,它完全符合@IsaacCarolWeisberg 的建议,即它创建了一个std::function,其中包含对@ 的引用987654335@ 包含原始函数......这让我认为没有中间调用就没有办法做到这一点。所以每次转化都会增加至少一个虚拟通话,有意思!
  • @Zelta 除非你有非常深的层次结构,否则我猜它不会有太大的不同。我的项目中的典型继承深度最多为 2 或 3。
【解决方案2】:

当两个类在同一个函数中未知时,有一个 C++ 特性允许从派生类转换为基类:异常。当然,这是一种丑陋、丑陋的滥用,但它确实有效:

#include <type_traits>
#include <stdexcept>

class StreamImplBase {
public:
    virtual void toss_input() = 0;
};

template <typename T>
class StreamImpl : public StreamImplBase {
public:
    virtual T* input() = 0;
    void toss_input() override
    { throw input(); }
};

template <typename T>
class Stream {
    StreamImplBase* impl;
public:
    template <typename U,
        std::enable_if_t<std::is_convertible<U*, T*>::value>* = nullptr>
    explicit Stream(StreamImpl<U>* impl) : impl(impl) {}

    template <typename U,
        std::enable_if_t<std::is_convertible<U*, T*>::value>* = nullptr>
    Stream(const Stream<U>& str) : impl(str.impl) {}

    T* input() const
    {
        try {
            impl->toss_input();
        } catch (T* ptr) {
            return ptr;
        }
        throw std::logic_error("Stream logic is broken?");
    }
};

查看complete example use on coliru。这可以通过使用std::shared_ptr&lt;StreamImpl&lt;U&gt;&gt;std::shared_ptr&lt;StreamImplBase&gt; 来改善。

【讨论】:

  • @Zelta 可能。我没有进行任何测量,但如果链接调用到单个转换的方法更快,即使链接了多个类型,我也不会感到惊讶。
猜你喜欢
  • 2019-11-08
  • 2016-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 2011-04-06
  • 1970-01-01
相关资源
最近更新 更多