web-dev-qa-db-ja.com

const参照が関数を介して渡される一時オブジェクトの寿命を延ばさないのはなぜですか?

次の簡単な例では、_ref2_をmin(x,y+1)の結果にバインドできないのはなぜですか?

_#include <cstdio>
template< typename T > const T& min(const T& a, const T& b){ return a < b ? a : b ; }

int main(){
      int x = 10, y = 2;
      const int& ref = min(x,y); //OK
      const int& ref2 = min(x,y+1); //NOT OK, WHY?
      return ref2; // Compiles to return 0
}
_

実例 -生成されるもの:

_main:
  xor eax, eax
  ret
_

EDIT:以下の例は状況をよりよく説明していると思います。

_#include <stdio.h>


template< typename T >
constexpr T const& min( T const& a, T const& b ) { return a < b ? a : b ; }



constexpr int x = 10;
constexpr int y = 2;

constexpr int const& ref = min(x,y);  // OK

constexpr int const& ref2 = min(x,y+1); // Compiler Error

int main()
{
      return 0;
}
_

実例 は以下を生成します:

_<source>:14:38: error: '<anonymous>' is not a constant expression

 constexpr int const& ref2 = min(x,y+1);

                                      ^

Compiler returned: 1
_
45

仕様によるものです。一言で言えば、一時がバインドされているnamed参照のみが直接にバインドされます寿命を延ばします。

[class.temporary]

5一時表現が完全な式の終わりとは異なる時点で破壊される3つのコンテキストがあります。 [...]

6 3番目のコンテキストは、参照が一時にバインドされている場合です。参照がバインドされている一時オブジェクト、または参照がバインドされているサブオブジェクトの完全なオブジェクトである一時オブジェクトは、次の場合を除いて、参照の存続期間中存続します。

  • 関数呼び出しで参照パラメーターにバインドされた一時オブジェクトは、呼び出しを含む完全式が完了するまで存続します。
  • 関数のreturnステートメントで返された値に一時的にバインドされる期間は延長されません。一時ファイルは、returnステートメントの全式の最後で破棄されます。
  • [...]

ref2に直接バインドせず、returnステートメントを介して渡します。この規格では、寿命を延ばすことは明記されていません。一部は特定の最適化を可能にします。しかし、最終的には、参照が関数に渡されたり、関数から渡されたりしたときに、どの一時ファイルを拡張する必要があるかを追跡することは、一般的に扱いにくいためです。

コンパイラーは、プログラムが未定義の動作を示さないという前提で積極的に最適化する場合があるため、その可能性のある兆候がわかります。存続期間外の値へのアクセスは定義されていません。これはreturn ref2;が行うことであり、動作は定義されていないため、単にゼロを返すことは有効です展示する行動。コンパイラによって契約が破られることはありません。

これは意図的なものです。参照は、一時直接にバインドされている場合にのみ、一時の有効期間を延長できます。コードでは、参照であるminの結果にref2をバインドしています。その参照が一時的なものであることは重要ではありません。 bのみが一時ファイルの寿命を延長します。 ref2も同じテンポラリを参照することは重要ではありません。

別の見方:オプションで有効期間を延長することはできません。これは静的プロパティです。 ref2が正しいことを行う場合tm、次にxおよびy+1のランタイム値に応じて、ライフタイムが延長されるかどうか。コンパイラーができることではありません。

17
Rakete1111

最初に質問に答えてから、答えの背景を説明します。 現在の草案には次の文言が含まれています:

参照がバインドされている一時オブジェクト、または参照がバインドされているサブオブジェクトの完全なオブジェクトである一時オブジェクトは、参照がバインドされているglvalueが次のいずれかによって取得された場合、参照の存続期間中存続します。 :

  • 一時的な実体化変換([conv.rval])、
  • (expression)、ここで、expressionはこれらの式の1つです。
  • 配列オペランドの添え字([expr.sub])。そのオペランドはこれらの式の1つです。
  • .演算子を使用したクラスメンバーアクセス([expr.ref])。左のオペランドはこれらの式の1つであり、右のオペランドは非参照型の非静的データメンバーを指定します。
  • .*演算子を使用したメンバーへのポインター操作([expr.mptr.oper])。左のオペランドはこれらの式の1つで、右のオペランドは非参照型のデータメンバーへのポインターです。
  • a const_­cast([expr.const.cast])、static_­cast([expr.static.cast])、dynamic_­cast([expr.dynamic.cast])、またはreinterpret_­cast([expr.reinterpret.cast])ユーザー定義の変換なしで、これらの式の1つであるglvalueオペランドを、オペランドで指定されたオブジェクトを参照するglvalue、またはその完全なオブジェクトまたはサブオブジェクトに変換するその、
  • 2番目または3番目のオペランドがこれらの式の1つであるglvalueである条件式([expr.cond])
  • 右のオペランドがこれらの式の1つであるglvalueであるコンマ式([expr.comma])。

これによると、参照が関数呼び出しから返されたglvalueにバインドされている場合、glvalueは関数呼び出しから取得されたものであり、これは寿命延長の許可された式の1つではありません。

一時パラメータy+1の有効期間は、参照パラメータbにバインドされると1回延長されます。ここでは、prvalue y+1が実体化されてxvalueが生成され、参照は一時的な実体化変換の結果にバインドされます。したがって、寿命延長が発生します。ただし、min関数が戻ると、ref2は呼び出しの結果にバインドされ、ここでは存続期間の延長は行われません。したがって、y+1テンポラリはref2の定義の最後に破棄され、ref2はぶら下がり参照になります。


このトピックについては、歴史的にいくつかの混乱がありました。 OPのコードと同様のコードがぶら下がっている参照になることはよく知られていますが、標準テキストは、C++ 17の時点でさえ、理由について明確な説明を提供していませんでした。

有効期間の延長は、参照が一時的に「直接」にバインドされた場合にのみ適用されるとしばしば主張されていますが、規格はその効果について何も述べていません。実際、標準は「直接バインドする」への参照の意味を定義し、その定義(egconst std::string& s = "foo";は間接参照ですバインディング)は、ここでは明らかに関係ありません。

Rakete1111は、SOの別の場所のコメントで、参照がprvalueにバインドする場合にのみ適用されると述べています(その一時オブジェクトへの以前の参照バインディングを通じて取得された一部のglvalueではなく)。ここでは「バインドされた...直接」と同様のことを言っているように見えますが、この理論に対するテキストによるサポートはありません。

struct S { int x; };
const int& r = S{42}.x;

ただし、C++ 14では、式S{42}.xがxvalueになったため、ここでライフタイム拡張が適用される場合、参照がprvalueにバインドするためではありません。

代わりに、存続期間の延長は1回だけ適用され、同じオブジェクトへの他の参照をバインドしてもその存続期間は延長されないと主張するかもしれません。これは、OPのコードがS{42}.xの場合に存続期間の延長を妨げることなく、ぶら下がり参照を作成する理由を説明しています。しかし、規格にもこの影響についての記述はありません。

StoryTellerは、参照は直接バインドする必要があるとここでも述べていますが、それが彼の意味するところもわかりません。彼は、returnステートメントで一時への参照をバインドしてもその寿命を延ばさないことを示す標準テキストを引用しています。ただし、このステートメントは、問題の一時ファイルがreturnステートメントの完全式によって作成された場合に適用されるように意図されているようです。式。明らかにそれはy+1一時の場合ではなく、代わりにminへの呼び出しを含む完全式の最後で破棄されます。したがって、私はこの発言が問題のそのような場合に適用することを意図していないと思う傾向があります。その代わり、その効果は、存続期間の拡張に関する他の制限と一緒に、一時オブジェクトの存続期間が、それが作成されたブロックスコープを超えて拡張されるのを防ぐことです。しかし、これは、質問の一時的なy+1mainの終わりまで存続することを妨げません。

したがって、質問は残ります。質問のref2を一時ファイルにバインドしても一時ファイルの寿命が延長されない理由を説明する原則は何ですか?

以前に引用した現在の草案の文言は、 CWG 1299 の解決策によって導入されました。これは2011年に公開されましたが、最近解決されました(C++ 17には間に合いません)。ある意味では、バインディングが存続期間の延長が発生するのに十分な「直接」であるケースを説明することにより、参照が「直接」にバインドする必要があるという直観を明確にします。ただし、参照がprvalueにバインドする場合にのみ許可されるほど制限的ではありません。 S{42}.xの場合、存続期間の延長を許可します。

8
Brian