Создать библиотеку для переопределения оператора *() итератора - указатель риска

Я пытаюсь создать свой собственный boost::adaptors::transformed.

Вот соответствующий буст-код.

Вот его использование (изменено из SO ответа от 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.
Это работает нормально в большинстве распространенных случаев.

Вот полная демонстрация и резервное копирование. (такой же, как приведенный ниже код)

Проблемной частью является 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
    }
}

Это приведет к ошибке во время выполнения. Вот демонстрация с висящим указателем.

В реальном использовании, если интерфейс более удивительный, например, используйте | Оператор, ошибка будет еще сложнее обнаружить: -

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

Вопрос

Как улучшить библиотеку, чтобы исправить эту ошибку, сохранив производительность, надежность и поддержание на одном уровне?

Другими словами, как элегантно кэшировать данные или указатель COLLECTION (это может быть адаптер или реальная структура данных)?

В качестве альтернативы, если легче ответить путем кодирования с нуля (чем модифицировать мой код), сделайте это. :)

Мои обходные пути

Текущий код кэшируется по указателю.
Основная идея обходных путей - вместо этого кэшировать по значению.

Обходной путь 1 (всегда "по значению")

Пусть адаптер кеширует значение COLLECTION.
Вот главное изменение:

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

Недостаток:-

  • Вся структура данных (например, std::vector) будет скопирована по значению - пустой ресурс.
    (при прямом использовании для std::vector)

Обходной путь 2 (две версии библиотеки, лучше?)

Я создам 2 версии библиотеки - AdapterValue и AdapterPointer.
Я должен создать связанные классы (Widget, AdapterIterator и т.д.).

  • AdapterValue - по значению. (разработан для utilityAdapter())
  • AdapterPointer - по указателю. (разработан для std::vector)

Недостаток:-

  • Много повторяющегося кода = низкая ремонтопригодность
  • Пользователи (кодеры) должны очень хорошо понимать, какой из них выбрать = низкая надежность

Обходной путь 3 (определить тип)

Я могу использовать специализацию шаблона, которая делает это:

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

Недостаток:-

  • Не очень хорошо взаимодействует между многими классами адаптеров.
    Они должны распознавать друг друга: распознанный= должен кэшировать по значению.

Извините за очень длинный пост.

Ответ 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
};

Внешний класс тогда покрывает оба случая, просто всегда используя указатели, в то время как вложенный класс скрывает различия.

Конечно, неполный (вам еще нужно добавить соответствующие конструкторы, например, как во внешний, так и во внутренний класс), но он должен дать вам идею...

Вы можете даже позволить пользователю выбрать, хочет ли он/она скопировать:

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;
};