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

jav*_*ver 15 c++ boost iterator adapter c++11

我正在努力创造自己的boost::adaptors::transformed.

这是相关的提升代码.

这是它的用法(由LogicStuff的SO答案修改): -

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 ....
}
Run Code Online (Sandbox Code Playgroud)

结果将与: -

for(auto b : test) {
    C c = funcPointer(b);
    //... something ...
}
Run Code Online (Sandbox Code Playgroud)

我的尝试

我创造了CollectAdapter这样的目标boost::adaptor::transformed.
它在大多数常见情况下都能正常工作

这是完整的演示备份.(与下面的代码相同)

有问题的部分是CollectAdapter- 我的图书馆的核心.
我不知道是否应该缓存collection_ by-pointerby-value.

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(){ ..... }
};
Run Code Online (Sandbox Code Playgroud)

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*());
    }
};
Run Code Online (Sandbox Code Playgroud)

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
    }
}  
Run Code Online (Sandbox Code Playgroud)

问题

上面的代码在大多数情况下都可以正常工作,除非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
    }
}
Run Code Online (Sandbox Code Playgroud)

这将导致运行时错误.这是悬空指针演示.

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

//inside "utilityAdapter(t)"
return t|func1;        //OK!
return t|func1|func2;  //dangling pointer
Run Code Online (Sandbox Code Playgroud)

如何改进我的库以修复此错误,同时保持性能稳健性以及维持在同一水平附近?

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

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

我的解决方法

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

解决方法1(始终"按值")

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

COLLECTION collection_;    //<------ #1 
//changed from   .... COLLECTION* collection_;
Run Code Online (Sandbox Code Playgroud)

坏处:-

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

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

我将创建2个版本的库 - AdapterValueAdapterPointer.
我要创建相关的类(Widget,AdapterIterator,等)为好.

  • AdapterValue - 按价值计算.(专为utilityAdapter())
  • AdapterPointer- 通过指针.(专为std::vector)

坏处:-

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

解决方法3(检测类型)

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

If( COLLECTION is an "CollectAdapter" ){ by value }  
Else{ by pointer }    
Run Code Online (Sandbox Code Playgroud)

坏处:-

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

很抱歉很长的帖子.

Aco*_*gua 1

我个人会选择模板专业化——但是,不是专业化原始模板,而是一个嵌套类:

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
};
Run Code Online (Sandbox Code Playgroud)

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

当然,不完整(您还需要添加适当的构造函数,例如,外部类和内部类),但它应该给您这个想法......

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

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;
};
Run Code Online (Sandbox Code Playgroud)