const参照によってデフォルトの引数の値を返すことは問題ありませんか?


26

以下の例のようにconst参照によってデフォルトの引数の値を返すことは問題ありませんか?

https://coliru.stacked-crooked.com/a/ff76e060a007723b

#include <string>

const std::string& foo(const std::string& s = std::string(""))
{
    return s;
}

int main()
{
    const std::string& s1 = foo();
    std::string s2 = foo();

    const std::string& s3 = foo("s");
    std::string s4 = foo("s");
}

3
簡単なテスト:std::string独自のクラスに置き換えて、建設と破壊を追跡できるようにします。
user4581301

1
@ user4581301シーケンスが正しい場合、コンストラクトに問題がないことは証明されません。
ピーター-モニカを復活させる

6
@ user4581301「試してみたところ機能したようです」は、未定義の動作についての最悪の事態です
HerrJoebob

質問は言い回しで誤解を招く一口であることに注意すべきです。const参照によってデフォルトの引数の値を返すのではなく、const参照へのconst参照(デフォルトの引数へ)を返します。
デイモン

2
@HerrJoebobステートメント100%に同意しますが、それを使用しているコンテキストには同意しません。私がそれを読む方法では、この質問は「オブジェクトの存続期間はいつですか?」に解決されます。デストラクタがいつ呼び出されるかを把握することは、そのための良い方法です。自動変数の場合、デストラクタは適切なタイミングで呼び出される必要があります。そうしないと、大きな問題が発生します。
user4581301

回答:


18

あなたのコードでは、両方s1s3参照がぶら下がっています。s2そしてs4大丈夫です。

最初の呼び出しではstd::string、デフォルトの引数から作成された一時的な空のオブジェクトが、呼び出しを含む式のコンテキストで作成されます。したがって、それはs1s1ぶら下がったままになるの定義の終わりに死にます。

2番目の呼び出しでは、一時std::stringオブジェクトを使用してを初期化しs2、それが終了します。

第三コールでは、文字列リテラルは"s"、一時的な作成するために使用されstd::stringたオブジェクトを、それはまたの定義の最後で死ぬs3残し、s3ダングリングを。

4番目の呼び出しではstd::string、値を持つ一時オブジェクトが"s"初期化に使用されてから終了しますs4

C ++ 17 [class.temporary] /6.1を参照

関数呼び出し(8.2.2)で参照パラメーターにバインドされた一時オブジェクトは、呼び出しを含む完全式が完了するまで存続します。


1
回答の興味深い部分は、デフォルトの引数が呼び出し元のコンテキストで作成されるという主張です。それはギヨームの標準的な見積もりによってサポートされているようです。
ピーター-モニカを復活させる

2
@ Peter-ReinstateMonica [expr.call] / 4、 "...を参照してください。各パラメータの初期化と破棄は、呼び出し元の関数のコンテキスト内で発生します。..."
Brian

8

安全ではありません

一般に、一時オブジェクトの寿命は、「それを渡す」ことによってさらに延長することはできません。一時ファイルがバインドされた参照から初期化された2番目の参照は、その寿命に影響を与えません。


それであなたstd::string s2 = foo();は有効だと思いますか(結局のところ、明示的に参照が渡されない)?
ピーター-モニカを復活させる

1
@ Peter-ReinstateMonicaは、新しいオブジェクトが構築されるので安全です。私の答えは単に生涯の拡大についてです。他の2つの回答はすでにすべてをカバーしています。私は繰り返しません。
Oblivion

5

それはあなたが後で文字列をどうするかによります。

あなたの質問が私のコードは正しいですか?そうです。

[dcl.fct.default] / 2

[ :宣言

void point(int = 3, int = 4);

int型の引数を0個、1個、または2個指定して呼び出すことができる関数を宣言します。次のいずれかの方法で呼び出すことができます。

point(1,2);  point(1);  point();

最後の2つの呼び出しは、それぞれpoint(1,4)およびと同等point(3,4)です。— 最後の例 ]

したがって、コードは実質的に以下と同等です。

const std::string& s1 = foo(std::string(""));
std::string s2 = foo(std::string(""));

すべてのコードは正しいですが、戻り値の型は参照であるため、これらのいずれの場合にも参照の有効期間の延長はありません。

テンポラリで関数を呼び出すので、返される文字列の有効期間はステートメントを延長しません。

const std::string& s1 = foo(std::string("")); // okay

s1; // not okay, s1 is dead. s1 is the temporary.

あなたの例s2は、あなたが満足の終わりの前に一時的なものからコピー(または移動)するので大丈夫です。s3と同じ問題がありs1ます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.