【问题标题】:Adapter of collection - omit T that depends on underlying collection集合的适配器 - 省略依赖于底层集合的 T
【发布时间】:2017-04-21 06:21:06
【问题描述】:

我正在尝试创建自定义集合 MyArray<T> 的适配器。
为简单起见,适配器Adapter 只做一件事:转换MyArray<T>::get 的返回结果。

(在实际情况下,MyArrayAdapter 是非常复杂的数据库操作符。)

版本 1

这是第一个版本,它可以工作。 (demo)

#include <iostream>
using namespace std;
template<class T>class MyArray{
    public: T* database[20];   
    public: T* get(int index){return database[index];} //<-important
    public: void set(int index,T* t){database[index]=t;} 
};
template<class T,class T2> class Adapter{
    public: MyArray<T>* underlying;
    public: void setUnderlying(MyArray<T>* pUnder){underlying=pUnder;}
    public: T2* get(int index){return static_cast<T2*>(underlying->get(index));}
    //^ "Adapter::get()" is encapsulating "MyArray::get()"
};
class B{};
class C:public B{}; 
class D:public C{};

int main() {
    MyArray<B> bs;
    bs.set(0,new C());  //some can be new D()
    //About the Adapter<C>, user is the one who sure that "bs" elements are "C*"-castable.
    Adapter<B,C> cs;       //<-- #1 need improve
    cs.setUnderlying(&bs); //<-- assign MyArray* to adapter
    C* c=cs.get(0);
    return 0;
}

版本 2

然后,我想牺牲性能来换取可读性和便利性。 (#1)
目标: 将模板参数的数量从 2 个 (Adapter&lt;B,C&gt;) 减少到 1 个 (Adapter&lt;C&gt;)。

这是我迄今为止的工作。它是可编译的,但在某些情况下会崩溃:-

class MyArrayBase{  //<--- new class
     public: virtual void* get(int index)=0;
};
template<class T>class MyArray : public MyArrayBase{
    public: T* database[20];
    public: T* get(int index){return database[index];}
    public: void set(int index,T* t){database[index]=t;}
};
template<class T2> class Adapter{
    public: MyArrayBase* underlying;  //<--- more abstraction 
    public: void setUnderlying(MyArrayBase* pUnder){underlying=pUnder;}
    public: T2* get(int index){return static_cast<T2*>(underlying->get(index));}   //#wrong
};
class B{};
class C:public B{};

int main() {
    MyArray<B> bs;
    bs.set(0,new C()); 
    Adapter<C> cs;   //<--- Yes! 1 template argument.
    cs.setUnderlying(&bs);
    C* c=cs.get(0);
    std::cout<<"hi"<<std::endl;
    return 0;
}

错误的原因:-
#wrongvoid*(底层B*)是static_castC*
这里是demo 说明是错的。 (打印 0 而不是 5)

问题

如何改进我的第一个版本代码以使Adapter 具有更少的模板参数?

标准:-

  • 不要使用函数指针。
    我觉得函数指针或std::function 是可能的,但它似乎是一个hack。
    我也想知道是否可以不使用
  • 开销不应(大致)比版本 2 中的单个虚拟调用 (v-table) 差。
  • 当调用static_cast&lt;C*&gt;(X*) 有意义时,Adapter&lt;C&gt;::setUnderlying 的单个实例必须能够接受任何MyArray&lt;X&gt;*
  • MyArrayAdapter 是库类。它对TT2 类型一无所知。
    例如,我不能将class MyArrayBase 中的void* 替换为B*

光线标准:-

  • 我更喜欢使用虚函数的解决方案。
  • 如果没有虚拟成本,那将是理想的,但我认为这是不可能的。

【问题讨论】:

  • 一种方法可能是将MyArray 中的T* get 更改为void*,以便实际使用get
  • @Winestone 这似乎是一个有趣的想法。我从来没有想过。我可能会尽量避免void*。谢谢。
  • 虽然不起作用,但只能消除警告。
  • @Winestone 同意,static_cast 也可以。顺便说一句,这是不雅的。我必须告诉用户一个奇怪的要求(元素必须派生自某个类 - Dummy)。这会降低库的质量/可用性(至少有一点)。
  • cs的构造函数中将bs传递给cs可以吗?然后您可以使用模板来推断第二个模板参数。您也可以拥有Adapter&lt;T2&gt;,然后让AdapterImpl&lt;T2, T&gt; 继承它并实现从T 转换为T2 的虚函数,然后声明Adapter&lt;C&gt;* cs = new AdapterImpl&lt;C, B&gt;();

标签: c++ templates data-structures c++14 virtual-functions


【解决方案1】:

您可以使用某种包装容器的包装器,通常:

// Here T = T2, you want a virtual function that already give you the right type
template <typename T>
class Wrapper {
public:
    virtual T* get(int index) const = 0;
};

// The real wrapper: It can give you T2 (To) but keep information
// about the original type since it is templated on Container
template <class To, class Container>
class WrapperContainer: public Wrapper<To> {
    Container *cont_;
public:  
    WrapperContainer(Container *cont) : cont_(cont) { }
    virtual To* get(int index) const override {
        return static_cast<To*>(cont_->get(index));
    }
};

包装器是您的 Adapter 之间的中间人,它只知道 To 类型(您要转换为的类型)和您的 MyArray 只知道 From 类型(您想要的类型转换自) - WrapperContainer 两者都知道,因此它可以在可能的情况下安全地从一个转换为另一个。

最后的Adapter

template<class T2> 
class Adapter {
    std::unique_ptr<Wrapper<T2>> w_; 
public:

    template <typename Container>
    void setUnderlying(Container *cont) {
        w_ = std::unique_ptr<Wrapper<T2>>(new WrapperContainer<T2, Container>(cont));
    }

    T2* get(int index)  {
        return w_->get(index);
    }
};

使用这个你不需要MyArray 的基类,因为你需要setUnderlyingMyArray&lt;B&gt; 推导出类型B

// No more need for a base class
template<class T>
class MyArray {
    T* database[20];
public: 
    T* get(int index){return database[index];}
    void set(int index,T* t){database[index]=t;}
};

您的代码的重要变化实际上是这一行:

return static_cast<To*>(cont_->get(index));

cont_-&gt;get(index) 的类型是 B*(在此示例中)而不是 void*,这使得转换工作。这也可以防止将setUnderlying 与不兼容类型的数组一起使用(尝试在下面的代码中取消注释cs.setUnderlying(&amp;as); 行)。

你可以在这里测试它:http://coliru.stacked-crooked.com/a/116305ec5f18b673

【讨论】:

  • 谢谢。我不希望有人会找到这个难题的答案! .... 通过一些工作(例如自定义分配器/char[]),std::unique_ptr 成本的(堆)间接也可以完全删除,对吧?
  • @javaLover 这可能有点复杂,因为您需要为 WrapperContainer 分配空间,这可能取决于 Container,我必须检查标准以查看是否有一种方法可以保证任何WrapperContainer 的最大空间。首先,我会说如果你为WrapperContainerContainer = void 保留空间(专门化以避免编译问题),你应该保证每个容器的空间,但我真的不是100% 确定。
  • 阅读您的解决方案的次数越多,我就越喜欢它。这真的很迷人。在Adapter 而不是MyArray 上进行虚拟化的想法非常有创意。我怎样才能把自己提高到你的一半呢?你有没有读过任何特定的书或者是经验?
猜你喜欢
  • 1970-01-01
  • 2012-11-20
  • 2014-06-23
  • 2016-08-25
  • 1970-01-01
  • 2012-07-05
  • 1970-01-01
  • 2013-02-07
  • 1970-01-01
相关资源
最近更新 更多