Я пытаюсь создать свой собственный 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 }
Недостаток:-
- Не очень хорошо взаимодействует между многими классами адаптеров.
Они должны распознавать друг друга: распознанный= должен кэшировать по значению.
Извините за очень длинный пост.