4

カスタム イテレータを使用してカスタム コンテナーを作成しました。コンテナーの特定の機能により、反復子は遅延評価する必要があります。質問のために、コードの関連部分は、このように実装された反復子の逆参照演算子です

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 4.9 では -O2) を使用すると、m_Current メンバーが変更可能としてマークされていても、最適化済みとしてマークした行がコンパイラによって最適化されることがあります。結果として、戻り値は反復子が指す値と一致しません。

これは予期される動作ですか? const とマークされていても、その関数の内容を評価する必要があることを指定する移植可能な方法を知っていますか?

質問が役立つほど網羅的であることを願っています。この場合、詳細が役立つかどうかアドバイスをお願いします。

編集:

1 つのコメントに答えるために、これは小さなテスト プログラムから取られた潜在的な使用法です。

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;

テスト結果は、最後の 2 行を除くすべてのテストで期待どおりです

最適化をオンにすると、テストの最後の 2 行が壊れます。

このシステムは実際にうまく機能し、他のイテレーターほど危険ではありません。もちろん、コンテナーが彼の鼻の下で削除された場合は失敗します。参照を保持するだけでなく、コピーによって返された値を使用する方がおそらく安全ですが、これはトピックから外れています

4

4 に答える 4

2

"メンバーがミュータブルm_Currentとしてマークされていても、最適化されています"

これは、オプティマイザが を気にかけていると仮定していることを示していますmutable。そうではありません。コンパイルの初期段階で取り除かれていますconstmutable

インライン化されている場合、なぜオプティマイザーは 2 つのステートメントを削除するのでしょうか? m_Current変数がすでに正しい値を保持している必要があるか、またはその後の m_Current自明なことに、次のケースでは、これらの書き込みがノーオペレーションになります。

Lazy_Iterator LI = foo(); // Theoretically writes
*LI = bar(); // Overwrites the previous value.
于 2016-03-03T12:28:35.020 に答える
2

reverse_iterator( によって返されるもの) が保持する物理イテレータとそれが指す論理値の違いに問題があります.base(): それらは 1 つずつずれています。dereferenceでreverse_iterator行う可能性がありreturn *(--internal_iterator);ます。これにより、破棄された関数ローカルの一時的な内部へのぶら下がり参照が残ります。

標準をもう一度読んだ後、そのようなシナリオを回避するための追加の要件があることがわかりました。注意を読んでください。

また、GCC 4.9 標準ライブラリが準拠していないこともわかりました。一時的に使用します。だから、GCCのバグだと思います。

編集:標準引用

24.5.1.3.4 operator* [reverse.iter.op.star]

reference operator*() const;

1効果:

deref_tmp = current;  
--deref_tmp; 
return *deref_tmp;

2 [ 注: この操作では、一時変数ではなく補助メンバー変数を使用して、関連付けられた反復子の有効期間を超えて持続する参照を返さないようにする必要があります。(24.2を参照してください。) —終わりの注]

フォローアップの読書: Library Defect Report 198

そして、以前の動作に戻ったようです。

後期編集: P0031 は C++17 ワーキング ドラフトで投票されました。reverse_iteratorメンバーではなく一時的な値を使用して中間値を保持すると述べています。

于 2016-03-03T12:44:24.147 に答える