首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >编译器优化中断延迟迭代器

编译器优化中断延迟迭代器
EN

Stack Overflow用户
提问于 2016-03-03 11:26:22
回答 3查看 386关注 0票数 4

我用自定义迭代器编写了一个自定义容器。由于容器的特定特性,必须延迟计算迭代器。为了解决这个问题,代码的相关部分是迭代器的反引用操作符,它是这样实现的。

代码语言:javascript
复制
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吗?

我希望这个问题足够详尽,足以有用。如果更多的细节在这个案例中有帮助的话,请提供建议。

编辑:

要回答一条评论,这是从一个小测试程序中获取的一种可能的用法:

代码语言:javascript
复制
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;

除了最后两行之外,所有测试的测试结果都与预期的一样。

当优化打开时,测试的最后两行崩溃。

该系统实际上运行良好,没有任何其他迭代器那么危险。当然,如果容器在他的鼻子下面被删除,那么它就会失败,而且通过复制使用返回的值可能更安全,而不仅仅是保留引用,但这是一个非主题。

EN

回答 3

Stack Overflow用户

发布于 2016-03-03 12:28:35

“即使m_Current成员标记为可变,也优化了它”

这告诉我,您假设优化器关心mutable。没有,constmutable已经被早期的编译阶段删除了。

那么,如果这两个语句是内联的,那么优化器为什么要删除它们呢?我怀疑,在内联之后,优化器可以证明这两种写操作都是无操作的,因为m_Current变量必须已经保存了正确的值,或者因为m_Current的后续使用使它变得毫无意义。简单地说,下面的情况使这些写成没有操作:

代码语言:javascript
复制
Lazy_Iterator LI = foo(); // Theoretically writes
*LI = bar(); // Overwrites the previous value.
票数 2
EN

Stack Overflow用户

发布于 2016-03-03 12:27:10

如果您必须发布一个可编译的片段来重现该问题(实际上,我无法用GCC 4.9来再现它),我认为您有未定义的行为,这是由O2触发的(O2支持可以打破未定义行为的优化)。你应该有一个指向

代码语言:javascript
复制
Container<T> 

在迭代器里面。

无论如何,要知道懒惰迭代器破坏了std迭代器的契约,我认为更好的选择是让一个常规容器包含惰性值,这样您就可以跳过创建定制容器和迭代器了;) (查看代理模式)。

票数 1
EN

Stack Overflow用户

发布于 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内部:

代码语言:javascript
复制
  /**
   *  @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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35770904

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档