在自定义迭代器上应用reverse_iterator后引用无效
Reference invalidation after applying reverse_iterator on a custom made iterator
我实现了一个双向迭代器,但它不是对数据结构进行操作,而是返回一个数学级数,可以在两个方向上迭代计算。事实上,我正在对整数进行迭代,在int上使用++和--。这意味着数据不会存储在不同的结构中,因此当迭代器超出范围时,值也会超出范围。
尽管如此,我还是希望下一个代码(最小失败示例)示例能够工作,因为迭代器始终处于作用域中。但它不起作用:(
#include <iostream>
#include <iterator>
#include <vector>
class my_iterator : public std::iterator<std::bidirectional_iterator_tag, int> {
int d_val = 12;
public:
my_iterator operator--(int) { std::cout << "decrement--n"; return my_iterator(); }
my_iterator &operator--() { std::cout << "--decrementn"; return *this; }
my_iterator operator++(int) { std::cout << "increment++n"; return my_iterator(); }
my_iterator &operator++() { std::cout << "++incrementn"; return *this; }
int &operator*() { std::cout << "*dereferencen"; return d_val; }
bool operator==(my_iterator const &o) { return false; }
bool operator!=(my_iterator const &o) { return true ; }
};
int main() {
auto it = std::reverse_iterator<my_iterator>();
int &i = *it;
if (true)
{
std::cout << i << 'n';
}
else
{
std::vector<int> vec;
vec.push_back(i);
std::cout << vec[0] << 'n';
}
}
来源:http://ideone.com/YJKvpl
if分支导致内存冲突,正如valgrind:正确检测到的那样
--decrement
*dereference
==7914== Use of uninitialised value of size 8
==7914== at 0x4EC15C3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4EC16FB: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x40087B: main (divine.cc:25)
==7914==
==7914== Conditional jump or move depends on uninitialised value(s)
==7914== at 0x4EC15CF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4EC16FB: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x40087B: main (divine.cc:25)
==7914==
==7914== Conditional jump or move depends on uninitialised value(s)
==7914== at 0x4EC1724: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==7914== by 0x40087B: main (divine.cc:25)
==7914==
12
==7914==
==7914== HEAP SUMMARY:
==7914== in use at exit: 0 bytes in 0 blocks
==7914== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==7914==
==7914== All heap blocks were freed -- no leaks are possible
==7914==
==7914== For counts of detected and suppressed errors, rerun with: -v
==7914== Use --track-origins=yes to see where uninitialised values come from
==7914== ERROR SUMMARY: 5 errors from 3 contexts (suppressed: 0 from 0)
其他分支不会导致内存冲突,或者至少在我的valgrind所能检测到的范围内。然而,存储在矢量中的值是"随机的":
--decrement
*dereference
-16777520
我对发生的事情有点惊讶。迭代器应该一直在作用域中,但引用似乎已失效。为什么在打印12时我会收到内存违规,或者为什么在存储与12不同的内容时我不会收到这些违规?
reverse_iterator
不适用于所谓的"隐藏迭代器",即返回对自身内容的引用的迭代器。reverse_iterator
的operator*
生成包装迭代器的副本,对其进行递减,并返回对副本的解引用结果。因此,如果取消引用迭代器返回对其内部某个内容的引用,则该引用将变为悬空。
在C++11规范中曾试图使其工作,但事实证明,如果不为非隐藏迭代器添加大量开销*,就不可能实现,因此该规范被恢复到C++03版本。
*要支持"隐藏迭代器",必须添加一个额外的数据成员来存储递减的当前迭代器,使reverse_iterator
的大小加倍;然后必须使用某种形式的同步,因为CCD_ 5是CCD_。对于这样一个不常见的用例,要向所有reverse_iterator
添加大量开销
如前所述,C++03和C++14标准以这种方式定义reverse_iterator::operator*
:
24.5.1.3.4
operator*
[反向iter.op.star]
reference operator*() const;
1效果:
Iterator tmp = current;
return *--tmp;
operator*
返回后,tmp
将被销毁,因此对存储在tmp
内部的数据的任何引用都将无效。C++11标准改变了这一点,并添加了一个注释:
24.5.1.3.4
operator*
[反向iter.op.star]
reference operator*() const;
1效果:
deref_tmp = current;
--deref_tmp;
return *deref_tmp;
2[注意:此操作必须使用辅助成员变量,而不是一个临时变量,用于避免返回持续超过其关联迭代器的生存期。(见24.2.)——尾注]
这实际上是不可能实现的,因为operator*
上有const
限定符,所以在C++11和C++14之间恢复了措辞。
最好的解决方案可能是基于针对任何版本的C++的C++11措辞来实现您自己的reverse_iterator
版本。幸运的是,该规范非常简单易懂。作为一个工作示例,下面是我为C++14编写的一个示例:
template <class Iterator>
class stashing_reverse_iterator :
public std::iterator<
typename std::iterator_traits<Iterator>::iterator_category,
typename std::iterator_traits<Iterator>::value_type,
typename std::iterator_traits<Iterator>::difference_type,
typename std::iterator_traits<Iterator>::pointer,
typename std::iterator_traits<Iterator>::reference
> {
typedef std::iterator_traits<Iterator> traits_type;
public:
typedef Iterator iterator_type;
typedef typename traits_type::difference_type difference_type;
typedef typename traits_type::reference reference;
typedef typename traits_type::pointer pointer;
stashing_reverse_iterator() : current() {}
explicit stashing_reverse_iterator(Iterator x) : current(x) {}
template <class U>
stashing_reverse_iterator(const stashing_reverse_iterator<U>& u) : current(u.current) {}
template <class U>
stashing_reverse_iterator& operator=(const stashing_reverse_iterator<U>& u) {
current = u.base();
return *this;
}
Iterator base() const {
return current;
}
// Differs from reverse_iterator::operator*:
// 1. const qualifier removed
// 2. current iterator is stored in a member field to ensure references are
// always valid after this function returns
reference operator*() {
deref_tmp = current;
--deref_tmp;
return *deref_tmp;
}
pointer operator->() const {
return std::addressof(operator*());
}
stashing_reverse_iterator& operator++() {
--current;
return *this;
}
stashing_reverse_iterator operator++(int) {
stashing_reverse_iterator tmp = *this;
--current;
return tmp;
}
stashing_reverse_iterator& operator--() {
++current;
return *this;
}
stashing_reverse_iterator operator--(int) {
stashing_reverse_iterator tmp = *this;
++current;
return tmp;
}
stashing_reverse_iterator operator+ (difference_type n) const {
return stashing_reverse_iterator(current - n);
}
stashing_reverse_iterator& operator+=(difference_type n) {
current -= n;
return *this;
}
stashing_reverse_iterator operator- (difference_type n) const {
return stashing_reverse_iterator(current + n);
}
stashing_reverse_iterator& operator-=(difference_type n) {
current += n;
return *this;
}
// Differs from reverse_iterator::operator[]:
// 1. const qualifier removed because this function makes use of operator*
reference operator[](difference_type n) {
return *(*this + n);
}
protected:
Iterator current;
private:
Iterator deref_tmp;
};
template <class Iterator1, class Iterator2>
bool operator==(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return x.base() == y.base(); }
template <class Iterator1, class Iterator2>
bool operator<(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return x.base() > y.base(); }
template <class Iterator1, class Iterator2>
bool operator!=(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return !(x.base() == y.base()); }
template <class Iterator1, class Iterator2>
bool operator>(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return x.base() < y.base(); }
template <class Iterator1, class Iterator2>
bool operator>=(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return x.base() <= y.base(); }
template <class Iterator1, class Iterator2>
bool operator<=(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y)
{ return x.base() >= y.base(); }
template <class Iterator1, class Iterator2>
auto operator-(
const stashing_reverse_iterator<Iterator1>& x,
const stashing_reverse_iterator<Iterator2>& y) -> decltype(y.base() - x.base())
{ return y.base() - x.base(); }
template <class Iterator>
stashing_reverse_iterator<Iterator> operator+(
typename stashing_reverse_iterator<Iterator>::difference_type n,
const stashing_reverse_iterator<Iterator>& x)
{ return stashing_reverse_iterator<Iterator>(x.base() - n); }
template <class Iterator>
stashing_reverse_iterator<Iterator> make_stashing_reverse_iterator(Iterator i)
{ return stashing_reverse_iterator<Iterator>(i); }
用法与reverse_iterator
:相同
// prints 5,4,3,2,1, for a sanely implemented number_iterator
std::copy(
make_stashing_reverse_iterator(number_iterator(5)),
make_stashing_reverse_iterator(number_iterator(0)),
std::ostream_iterator<int>(std::cout, ","));
- 将对象数组的引用传递给函数
- 什么时候在C++中返回常量引用是个好主意
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 何时在引用或唯一指针上使用移动语义
- 如何在c++中使用引用实现类似python的行为
- 编译C++时未定义的引用
- Ctypes wstring通过引用传递
- c++r值引用应用于函数指针
- 理解c++中的引用
- C++取消引用指针.为什么会发生变化
- 如何修复此错误:未定义对"距离(浮点数,浮点数,浮点数,浮点数,浮点数)"的引用
- 我的项目不会像"undefined reference to `grpc::g_core_codegen_interface'"那样使用未定义的引用错误进行编译
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- 强制转换为引用类型
- 引用一个已擦除类型(void*)的指针
- 向量元素的引用地址与它所指向的向量元素的地址不同.为什么
- 具有默认值的引用获取函数
- 如何使用基类指针引用派生类成员
- std::iterator::reference 必须是引用吗?
- c++ std::map::iterator一无所获,但下标操作符返回对映射值的引用