33

GCC の実装std::initializer_listは、return full-expression の最後で関数から返された配列を破棄します。これは正しいです?

このプログラムの両方のテスト ケースは、値が使用される前にデストラクタが実行されることを示しています。

#include <initializer_list>
#include <iostream>

struct noisydt {
    ~noisydt() { std::cout << "destroyed\n"; }
};

void receive( std::initializer_list< noisydt > il ) {
    std::cout << "received\n";
}

std::initializer_list< noisydt > send() {
    return { {}, {}, {} };
}

int main() {
    receive( send() );
    std::initializer_list< noisydt > && il = send();
    receive( il );
}

プログラムは機能するはずだと思います。しかし、基礎となる標準は少し複雑です。

return ステートメントは、宣言されているかのように戻り値オブジェクトを初期化します。

std::initializer_list< noisydt > ret = { {},{},{} };

これにより、指定された一連のイニシャライザから 1 つの一時initializer_listストレージとその下にある配列ストレージが初期化され、次にinitializer_list最初のものから別のストレージが初期化されます。アレイの寿命は? initializer_list「配列の寿命はオブジェクトの寿命と同じです。」しかし、そのうちの 2 つがあります。どちらが曖昧です。8.5.4/6 の例は、宣伝どおりに機能する場合、コピー先オブジェクトの有効期間が配列にあるというあいまいさを解決する必要があります。次に、戻り値の配列も呼び出し元の関数に残り、名前付き参照にバインドして保存できるようにする必要があります。

LWSでは、GCC は返される前に配列を誤って削除しますが、例に従って名前付きを保持しますinitializer_list。Clang も例を正しく処理しますが、リスト内のオブジェクトが破棄されることはありません。これにより、メモリ リークが発生します。ICC はまったくサポートinitializer_listしていません。

私の分析は正しいですか?


C++11 §6.6.3/2:

波括弧初期化リストを持つ return ステートメントは、指定された初期化子リストからのコピー リスト初期化 (8.5.4) によって、関数から返されるオブジェクトまたは参照を初期化します

8.5.4/1:

… copy-initialization コンテキストでの list-initialization はcopy-list-initializationと呼ばれます。

8.5/14:

T x = a;…の形式で行われる初期化は、 copy-initializationと呼ばれます。

8.5.4/3 に戻る:

タイプ T のオブジェクトまたは参照のリスト初期化は、次のように定義されます: …</p>

— それ以外の場合、T が の特殊化であるstd::initializer_list<E>場合、initializer_listオブジェクトは以下で説明するように構築され、同じ型のクラスからのオブジェクトの初期化の規則に従ってオブジェクトを初期化するために使用されます (8.5)。

8.5.4/5:

型のオブジェクトは、実装が型EのN 個std::initializer_list<E>の要素の配列を割り当てたかのように、初期化子リストから構築されます。ここで、Nは初期化子リスト内の要素の数です。その配列の各要素は、初期化子リストの対応する要素でコピー初期化され、オブジェクトはその配列を参照するように構築されます。要素のいずれかを初期化するために縮小変換が必要な場合、プログラムは不適切な形式です。std::initializer_list<E>

8.5.4/6:

initializer_list配列の寿命は、オブジェクトの寿命と同じです。[例:

typedef std::complex<double> cmplx;
 std::vector<cmplx> v1 = { 1, 2, 3 };
 void f() {
   std::vector<cmplx> v2{ 1, 2, 3 };
   std::initializer_list<int> i3 = { 1, 2, 3 };
 }

v1との場合、 forで作成さv2れたinitializer_listオブジェクトと配列{ 1, 2, 3 }は完全な式の有効期間を持ちます。の場合i3、initializer_list オブジェクトと配列には自動有効期間があります。— 最後の例]


ブレース初期化リストを返すことについて少し説明

中括弧で囲まれた裸のリストを返すと、

波括弧初期化リストを持つ return ステートメントは、指定された初期化子リストからのコピー リスト初期化 (8.5.4) によって、関数から返されるオブジェクトまたは参照を初期化します。

これは、呼び出しスコープに返されたオブジェクトが何かからコピーされたことを意味するものではありません。たとえば、これは有効です。

struct nocopy {
    nocopy( int );
    nocopy( nocopy const & ) = delete;
    nocopy( nocopy && ) = delete;
};

nocopy f() {
    return { 3 };
}

これではありません:

nocopy f() {
    return nocopy{ 3 };
}

copy-list-initialization はnocopy X = { 3 }、戻り値を表すオブジェクトを初期化するために構文と同等のものを使用することを意味します。これはコピーを呼び出さず、配列の有効期間が延長される 8.5.4/6 の例とたまたま同じです。

そして、Clang と GCC はこの点で一致しています。


その他の注意事項

N2640のレビューでは、このコーナー ケースについての言及はありません。ここに組み合わされた個々の機能については広範な議論がありましたが、それらの相互作用については何もわかりません。

オプションの可変長配列を値で返すことになるため、これを実装するのは面倒です。はそのコンテンツを所有していないためstd::initializer_list、関数は所有する別のものも返す必要があります。関数に渡す場合、これは単なるローカルの固定サイズの配列です。std::initializer_listただし、逆方向では、VLA をのポインターと共にスタックに返す必要があります。次に、シーケンスを破棄するかどうか (シーケンスがスタック上にあるかどうか) を呼び出し元に通知する必要があります。

この問題は、ラムダ関数からブレース初期化リストを返すことで、非常に簡単に遭遇します。これは、いくつかの一時オブジェクトがどのように含まれているかを気にせずに返す「自然な」方法です。

auto && il = []() -> std::initializer_list< noisydt >
               { return { noisydt{}, noisydt{} }; }();

確かに、これは私がここに来た方法と似ています。ただし、->ラムダの戻り値の型推定は式が返されたときにのみ発生し、波括弧初期化リストは式ではないため、trailing-return-type を省略するとエラーになります。

4

2 に答える 2

23

std::initializer_listはコンテナではありません。値を渡したり、値が持続することを期待したりするために使用しないでください。

DR 1290は文言を変更しました。また、まだ準備ができていない15651599にも注意する必要があります。

次に、戻り値の配列も呼び出し元の関数に存続する必要があり、名前付き参照にバインドすることでそれを保持できるはずです。

いいえ、それは続きません。アレイの存続期間は、とともに延長され続けることはありませんinitializer_list。検討:

struct A {
    const int& ref;
    A(const int& i = 0) : ref(i) { }
};

参照iは一時的なにバインドされ、int次に参照refもそれにバインドされますが、それはの存続期間を延長せずi、コンストラクターの最後でスコープ外になり、ぶら下がっている参照が残ります。別の参照をバインドして、基になる一時の有効期間を延長することはありません。

1565が承認され、参照ではなくコピーを作成した場合、コードはより安全になる可能性がありますが、その問題は未解決であり、実装経験は言うまでもなく、文言も提案されていません。il

あなたの例が機能することを意図しているとしても、基礎となる配列の存続期間に関する表現は明らかにまだ改善されており、コンパイラーが最終的なセマンティクスが決まったものを実装するのにしばらく時間がかかります。

于 2013-03-08T09:54:37.203 に答える