У меня есть итератор DataIterator
который производит значения по требованию, поэтому оператор разыменования возвращает данные, а не данные&, Я думал, что это нормально, пока не попытался сторнировать DataIterator данных, поместив его в reverse_iterator.
DataCollection collection
std::reverse_iterator<DataIterator> rBegin(iter) //iter is a DataIterator that's part-way through the collection
std::reverse_iterator<DataIterator> rEnd(collection.cbegin());
auto Found = std::find_if(
rBegin,
rEnd,
[](const Data& candidate){
return candidate.Value() == 0x00;
});
Когда я запускаю приведенный выше код, он никогда не находит объект Data, значение которого равно 0, хотя я знаю, что он существует. Когда я вставляю точку останова в предикат, я вижу странные значения, которые я никогда не ожидал бы увидеть как 0xCCCC — вероятно, неинициализированная память. Что происходит, так это то, что оператор разыменования reverse_iterator выглядит так (из xutility — Visual Studio 2010)
Data& operator*() const
{ // return designated value
DataIterator _Tmp = current;
return (*--_Tmp); //Here's the problem - the * operator on DataIterator returns a value instead of a reference
}
Последняя строка — где проблема — создаются временные данные и возвращается ссылка на эти данные. Ссылка недействительна сразу.
Если я изменю свой предикат в std :: find_if, чтобы принять (кандидат данных) вместо (постоянные данные)& кандидат) тогда предикат работает — но я уверен, что мне просто повезло с неопределенным поведением там. Ссылка недействительна, но я делаю копию данных до того, как память будет засорена.
Что я могу сделать?
Могу ли я что-то сделать с DataIterator, чтобы не дать кому-то еще полдня выяснить, что не так, когда они попробуют то же самое через 6 месяцев?
Не то чтобы я большой поклонник этой идеи, но если вы выделите кучу Data
объект, а затем вернул ссылку на shared_ptr
для него это позволило бы внешнему миру удерживать его дольше, если это необходимо, и для вас «забыть» об этом, когда вы сделаете шаг вперед.
С другой стороны, реализация вашего собственного нативного reverse_iterator
может быть большая победа. Это то, что я сделал для своего собственного связанного списка, так как я не использовал объект-страж вроде gcc
делает и не мог использовать std::reverse_iterator
, Это действительно было не так сложно.
Это потому что reverse_iterator
Интерфейс был разработан до существования decltype
, Сегодня это будет написано как
auto operator*() const -> decltype(*current)
{ // return designated value
DataIterator _Tmp = current;
return (*--_Tmp);
}
а также в C ++ 14, даже конечный тип возврата не понадобится, так как он может быть выведен.
decltype(auto) operator*() const
{ // return designated value
DataIterator _Tmp = current;
return (*--_Tmp);
}
Я закончил тем, что пошел с предложением Кейси в комментариях. Он не опубликовал это как ответ, который я могу принять, поэтому я сам напишу это.
Я сделал специализацию reverse_iterator для DataIterator, который возвращает значение вместо ссылки. Это включало копирование / вставку реализации из xutility, указание одного из аргументов шаблона в качестве DataIterator и изменение
reference operator*() const
в
value operator*() const