我用自定义迭代器编写了一个自定义容器。由于容器的特定特性,必须延迟计算迭代器。为了解决这个问题,代码的相关部分是迭代器的反引用操作符,它是这样实现的。
template<typename T>
struct Container
{
vector<T> m_Inner;
// This should calculate the appropriate value.
// In this example is taken from a vec but in
//the real use-case is calculated on request
T Value(int N)
{ m_Inner.at(N); }
}
template<typename T>
struct Lazy_Iterator
{
mutable pair<int, T> m_Current;
int Index
Container<T>* C
Lazy_Iterator(const Container& Cont, int N):
m_Current{Index, T{}}, Index{N}, C{&Cont}
{ }
pair<int, T>&
operator*() const // __attribute__((noinline)) (this cures the symptom)
{
m_Current.first = Index; /// Optimized out
m_Current.second = C->Value(Index); /// Optimized out
return m_Current;
}
}因为迭代器本身是一个模板,它的函数可以由编译器自由内联。
当我在没有优化的情况下编译代码时,返回的值会按预期更新。在某些情况下,当我使用发行版编译器优化( GCC中的-O2)时,编译器会优化我标记为优化的行,即使m_Current成员标记为可变的。因此,返回值与迭代器应该指向的值不匹配。
这是一种预期的行为吗?您知道任何可移植的方法来指定应该评估该函数的内容,即使它被标记为const吗?
我希望这个问题足够详尽,足以有用。如果更多的细节在这个案例中有帮助的话,请提供建议。
编辑:
要回答一条评论,这是从一个小测试程序中获取的一种可能的用法:
Container<double> myC;
Lazy_Iterator<double> It{myC, 0}
cout << "Creation: " << it->first << " , " << it->second << endl;
auto it2 = it;
cout << "Copy: "<< it2->first << " , " << it2->second << endl;
cout << "Pre-increment: " << (it++)->first << " , " << it->second << endl;
cout << "Post-increment: " << (++it)->first << " , " << it->second << endl;
cout << "Pre-decrement: " << (it--)->first << " , " << it->second << endl;
cout << "Post-decrement: " << (--it)->first << " , " << it->second << endl;
cout << "Iterator addition: " << (it+2)->first << " , " << (it+2)->second << endl;
cout << "Iterator subtraction: "<< (it-2)->first << " , " << (it-2)->second << endl;
reverse_iterator<Lazy_Iterator> rit{it};
cout << "Reverse Iterator: " << rit->first << " , " << rit->second << endl;
auto rit2 = rit;
cout << "Reverse Iterator copy: " << rit2->first << " , " << rit2->second << endl;
cout << "Rev Pre-increment: " << (rit++)->first << " , " << rit->second << endl;
cout << "Rev Post-increment: " << (++rit)->first << " , " << rit->second << endl;
cout << "Rev Pre-decrement: " << (rit--)->first << " , " << rit->second << endl;
cout << "Rev Post-decrement: " << (--rit)->first << " , " << rit->second << endl;
cout << "Rev Iterator addition: " << (rit+2)->first << " , " << (rit+2)->second << endl;
cout << "Rev Iterator subtraction: "<< (rit-2)->first << " , " << (rit-2)->second << endl;除了最后两行之外,所有测试的测试结果都与预期的一样。
当优化打开时,测试的最后两行崩溃。
该系统实际上运行良好,没有任何其他迭代器那么危险。当然,如果容器在他的鼻子下面被删除,那么它就会失败,而且通过复制使用返回的值可能更安全,而不仅仅是保留引用,但这是一个非主题。
发布于 2016-03-03 12:28:35
“即使m_Current成员标记为可变,也优化了它”
这告诉我,您假设优化器关心mutable。没有,const和mutable已经被早期的编译阶段删除了。
那么,如果这两个语句是内联的,那么优化器为什么要删除它们呢?我怀疑,在内联之后,优化器可以证明这两种写操作都是无操作的,因为m_Current变量必须已经保存了正确的值,或者因为m_Current的后续使用使它变得毫无意义。简单地说,下面的情况使这些写成没有操作:
Lazy_Iterator LI = foo(); // Theoretically writes
*LI = bar(); // Overwrites the previous value.发布于 2016-03-03 12:27:10
如果您必须发布一个可编译的片段来重现该问题(实际上,我无法用GCC 4.9来再现它),我认为您有未定义的行为,这是由O2触发的(O2支持可以打破未定义行为的优化)。你应该有一个指向
Container<T> 在迭代器里面。
无论如何,要知道懒惰迭代器破坏了std迭代器的契约,我认为更好的选择是让一个常规容器包含惰性值,这样您就可以跳过创建定制容器和迭代器了;) (查看代理模式)。
发布于 2016-03-03 13:07:31
在进行了一轮非常有益的讨论之后,Revolver_Ocelot的回答使我进一步关注了reverse_iterators的实现。根据他从标准中引用的话:
24.5.1.3.4算子* reverse.iter.op.star
reference operator*() const;1效果: deref_tmp =当前;-deref_tmp;返回*deref_tmp; 注:此操作必须使用辅助成员变量,而不是临时变量,以避免返回超过关联迭代器生存期的引用。(见24.2)-end注
查看由GCC 4.9在Debian 8中实现的标准库的标头stl_iterator.c内部:
/**
* @return A reference to the value at @c --current
*
* This requires that @c --current is dereferenceable.
*
* @warning This implementation requires that for an iterator of the
* underlying iterator type, @c x, a reference obtained by
* @c *x remains valid after @c x has been modified or
* destroyed. This is a bug: http://gcc.gnu.org/PR51823
*/
reference
operator*() const
{
_Iterator __tmp = current;
return *--__tmp;
}注意警告:
警告:此实现要求对于基础迭代器类型的迭代器@c,@c * x获得的引用在@c被修改或销毁后仍然有效。这是一个bug:http://gcc.gnu.org/PR51823
https://stackoverflow.com/questions/35770904
复制相似问题