创建库以覆盖迭代器的运算符*() - 冒险悬挂指针

时间:2017-02-21 07:05:56

标签: c++ c++11 boost iterator adapter

我正在尝试创建自己的boost::adaptors::transformed

以下是相关的boost code

以下是它的用法(从a SO answer by LogicStuff修改): -

C funcPointer(B& b){ 
    //"funcPointer" is function convert from "B" to "C"
    return instance-of-C
}

MyArray<B> test;  //<-- any type, must already have begin() & end()

for(C c : test | boost::adaptor::transformed(funcPointer)) {
    //... something ....
}

结果将与: -

相同
for(auto b : test) {
    C c = funcPointer(b);
    //... something ...
}

我的尝试

我创建的CollectAdapter旨在像boost::adaptor::transformed一样工作 它在大多数常见情况下都能正常工作

以下是完整的demoback up(与以下代码相同)

有问题的部分是CollectAdapter - 我图书馆的核心 我不知道是否应该缓存collection_ 按指针按值

CollectAdapter 封装基础collection_(例如指向std::vector<>的指针): -

template<class COLLECTION,class ADAPTER>class CollectAdapter{
    using CollectAdapterT=CollectAdapter<COLLECTION,ADAPTER>;
    COLLECTION* collection_;    //<---- #1  problem? should cache by value?
    ADAPTER adapter_;           //<---- = func1 (or func2)
    public: CollectAdapter(COLLECTION& collection,ADAPTER adapter){
        collection_=&collection;
        adapter_=adapter;
    }
    public: auto begin(){
        return IteratorAdapter<
            decltype(std::declval<COLLECTION>().begin()),
            decltype(adapter_)>
            (collection_->begin(),adapter_);
    }
    public: auto end(){ ..... }
};

IteratorAdapter (上面用过)封装了底层迭代器,改变了operator*的行为: -

template<class ITERATORT,class ADAPTER>class IteratorAdapter : public ITERATORT {
    ADAPTER adapter_;
    public: IteratorAdapter(ITERATORT underlying,ADAPTER adapter) :
        ITERATORT(underlying),
        adapter_(adapter)
    {   }
    public: auto operator*(){
        return adapter_(ITERATORT::operator*());
    }
};

CollectAdapterWidget (在下面使用)只是构建 CollectAdapter -instance的辅助类。

可以像: -

一样使用
int func1(int i){   return i+10;   }
int main(){
    std::vector<int> test; test.push_back(5);
    for(auto b:CollectAdapterWidget::createAdapter(test,func1)){
        //^ create "CollectAdapter<std::vector<int>,func1>" instance
         //here, b=5+10=15
    }
}  

问题

上述代码在大多数情况下都可以正常工作,除非COLLECTION是临时对象。

更具体地说,当我创建适配器适配器 适配器 时,可能会发生悬空指针。

int func1(int i){   return i+10;    }
int func2(int i){   return i+100;   }
template<class T> auto utilityAdapter(const T& t){
    auto adapter1=CollectAdapterWidget::createAdapter(t,func1);
    auto adapter12=CollectAdapterWidget::createAdapter(adapter1,func2);
    //"adapter12.collection_" point to "adapter1"
    return adapter12;
    //end of scope, "adapter1" is deleted
    //"adapter12.collection_" will be dangling pointer
}
int main(){
    std::vector<int> test;
    test.push_back(5);
    for(auto b:utilityAdapter(test)){
        std::cout<< b<<std::endl;   //should 5+10+100 = 115
    }
}

这将导致运行时错误。这是the dangling-pointer demo

在实际使用中,如果界面更加精彩,例如使用|运算符,将更难以检测到错误: -

//inside "utilityAdapter(t)"
return t|func1;        //OK!
return t|func1|func2;  //dangling pointer

问题

如何改善我的库以修复此错误,同时保持性能&amp; 健壮性&amp; 可维持接近同一水平?

换句话说,如何优雅地缓存COLLECTION(可以是适配器真实数据结构)的数据或指针?

或者,如果从头开始编码(而不是修改我的代码)更容易回答,那就去吧。 :)

我的解决方法

当前代码通过指针缓存 解决方法的主要思想是缓存按值

解决方法1(始终&#34;按值&#34;)

适配器缓存COLLECTION
这是主要的变化: -

COLLECTION collection_;    //<------ #1 
//changed from   .... COLLECTION* collection_;

缺点: -

  • 整个数据结构(例如std::vector)将被价值复制 - 浪费资源 (直接用于std::vector时)

解决方法2(两个版本的库,最好?)

我将创建2个版本的库 - AdapterValueAdapterPointer 我还必须创建相关的类(WidgetAdapterIterator等)。

  • AdapterValue - 按值。 (专为utilityAdapter()设计)
  • AdapterPointer - 指针。 (专为std::vector设计)

缺点: -

  • 重复代码=低可维护性
  • 用户(程序员)必须非常清楚选择哪一个=低稳健性

解决方法3(检测类型)

我可以使用模板专业化来执行此操作: -

If( COLLECTION is an "CollectAdapter" ){ by value }  
Else{ by pointer }    

缺点: -

  • 许多适配器类之间不合作 他们必须互相认识:已识别 =应按值>缓存

很抱歉很长的帖子。

2 个答案:

答案 0 :(得分:0)

我个人将模板专门化–但是,不是专门针对原始模板,而是嵌套类:

template<typename Collection, typename Adapter>
class CollectAdapter
{
    template<typename C>
    class ObjectKeeper // find some better name yourself...
    {
        C* object;
    public:
        C* operator*() { return object; };
        C* operator->() { return object; };
    };
    template<typename C, typename A>
    class ObjectKeeper <CollectAdapter<C, A>>
    {
        CollectAdapter<C, A> object;
    public:
        CollectAdapter<C, A>* operator*() { return &object; };
        CollectAdapter<C, A>* operator->() { return &object; };
    };

    ObjectKeeper<Collection> keeper;

    // now use *keeper or keeper-> wherever needed
};

然后,外部类通过始终使用指针来覆盖这两种情况,而嵌套类将差异隐藏起来。

当然,不完整(例如,您还需要向外部和内部类中添加适当的构造函数),但这应该可以给您一个主意...

您甚至可以允许用户选择是否要复制:

template<typename Collection, typename Adapter, bool IsAlwaysCopy = false>
class CollectAdapter
{
    template<typename C, bool IsCopy>
    class ObjectWrapper // find some better name yourself...
    {
        C* object;
    public:
        C* operator*() { return object; };
        C* operator->() { return object; };
    };
    template<typename C>
    class ObjectWrapper<C, true>
    {
        C object;
    public:
        C* operator*() { return &object; };
        C* operator->() { return &object; };
    };

    // avoiding code duplication...
    template<typename C, bool IsCopy>
    class ObjectKeeper : public ObjectWrapper<C, IsCopy>
    { };
    template<typename C, typename A, bool IsCopy>
    class ObjectKeeper <CollectAdapter<C, A>, IsCopy>
        : public ObjectWrapper<CollectAdapter<C, A>, true>
    { };

    ObjectKeeper<Collection> keeper;
};

答案 1 :(得分:0)

在我的indexed_view中,如果集合是右值,则存储集合的值;如果是左值,则存储引用。您可以在此处执行相同操作:重载operator|的右值和左值。

template<typename Collection,typename Filter>
auto operator|(Collection&& collection,Filter filter){
   return create_adapter_for_rvalue_collection(collection,filter);
}
template<typename Collection,typename Filter>
auto operator|(Collection const& collection,Filter filter){
   return create_adapter_for_const_lvalue_collection(collection,filter);
}
template<typename Collection,typename Filter>
auto operator|(Collection & collection,Filter filter){
   return create_adapter_for_non_const_lvalue_collection(collection,filter);
}