11

C++ 11で配列を宣言および初期化する8つの方法は次のg++とおりです。

/*0*/ std::array<int, 3> arr0({1, 2, 3});
/*1*/ std::array<int, 3> arr1({{1, 2, 3}});
/*2*/ std::array<int, 3> arr2{1, 2, 3};
/*3*/ std::array<int, 3> arr3{{1, 2, 3}};
/*4*/ std::array<int, 3> arr4 = {1, 2, 3};
/*5*/ std::array<int, 3> arr5 = {{1, 2, 3}};
/*6*/ std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
/*7*/ std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});

厳密な標準 (および今後の C++14 標準) に従って正しいものは何ですか? 最も一般的/使用されているものと避けるべきもの (およびその理由) は?

4

4 に答える 4

19

C++11 のまとめ / TL;DR

  • ブレースの省略の欠陥のため、例 0、2、6 は動作する必要はありません。ただし、コンパイラの最近のバージョンでは、その欠陥に対して提案された解決策が実装されているため、これらの例が機能します。
  • std::array生の配列が含まれているかどうかは指定されていないためです。したがって、例 1、3、5、7 は機能する必要はありません。ただし、(実際には)機能しない標準ライブラリの実装については知りません。
  • 例 4 は常に機能します。std::array<int, 3> arr4 = {1, 2, 3};

バージョン 4 またはバージョン 2 (ブレースの省略を修正したもの) をお勧めします。これらは直接初期化され、動作する可能性が高い/必要なためです。

Sutter の AAA スタイルの場合、 を使用できますauto arrAAA = std::array<int, 3>{1, 2, 3};が、これにはブレース省略の修正が必要です。


std::array集約 [array.overview]/2 である必要があります。これは、ユーザー提供のコンストラクターがないことを意味します (つまり、default、copy、move ctor のみ)。


std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});

での初期化(..)は直接初期化です。これには、コンストラクターの呼び出しが必要です。arr0との場合arr1、コピー/移動コンストラクターのみが実行可能です。したがって、これらの 2 つの例は、braced-init-list からテンポラリを作成し、std::arrayそれを destination にコピー/移動することを意味します。コピー/移動の省略により、たとえ副作用があっても、コンパイラはそのコピー/移動操作を省略できます。

注意: 一時変数は prvalues ですが、std::array削除された場合など、移動 ctor が暗黙的に宣言されていない可能性があるため、コピーを呼び出す可能性があります (意味的に、コピー省略の前に)。


std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});

これらはコピー初期化の例です。次の 2 つの一時ファイルが作成されます。

  • ブレース初期化リストを介して{1, 2, 3}コピー/移動コンストラクターを呼び出す
  • 表現を通してstd::array<int, 3>(..)

後者の一時変数は、指定された宛先変数にコピー/移動されます。両方の一時的な作成は省略できます。

私の知る限り、実装はexplicit array(array const&) = default;コンストラクターを記述でき、標準に違反することはありません。これにより、これらの例が不適切な形式になります。(この可能性は [container.requirements.general] によって除外されています。David Krauss に敬意を表します。この議論を参照してください。)


std::array<int, 3> arr2{1, 2, 3};
std::array<int, 3> arr3{{1, 2, 3}};
std::array<int, 3> arr4 = {1, 2, 3};
std::array<int, 3> arr5 = {{1, 2, 3}};

これが集約初期化です。std::arrayそれらはすべて、のコンストラクターを呼び出したり、std::array(意味的に) 一時配列を作成したりせずに、 を「直接」初期化します。のメンバーは、std::arrayコピー初期化によって初期化されます (以下を参照)。


ブレース省略のトピックについて:

C++11 標準では、ブレースの省略はフォームの宣言にのみ適用され、 には適用されT x = { a };ませんT x { a };。これは欠陥と見なされ、C++1y で修正される予定ですが、提案された解決策は標準の一部ではありません (DRWP ステータス、リンクされたページの上部を参照) T x { a };

したがって、std::array<int, 3> arr2{1, 2, 3};(例 0、2、6) は厳密に言えば形式が正しくありません。私の知る限り、clang++ と g++ の最近のバージョンでは、ブレースの省略がT x { a };既に許可されています。

例 6 では、std::array<int, 3>({1, 2, 3})copy-initialization を使用しています。引数を渡すための初期化も copy-init です。ただし、ブレース省略の欠陥のある制限「フォームの宣言ではT x = { a };は、引数の受け渡しのブレース省略も禁止します。これは、宣言ではなく、確かにその形式ではないためです。


集約初期化のトピックについて:

Johannes Schaubがコメントで指摘しているように、次の構文 [array.overview]/2 でa を初期化できることが保証されているだけです。std::array

array<T, N> a = {初期化リスト};

そのことから、form でブレース省略が許可されている場合T x { a };次の構文を推測できます。

array<T, N> a {初期化リスト};

整形式で、同じ意味です。ただし、std::array唯一のデータ メンバーとして raw 配列が実際に含まれているとは限りません ( LWG 2310も参照)。std::array<T, 2>1 つの例として、2 つのデータ メンバーT m0とがある部分的な特殊化が考えられますT m1。したがって、次のように結論付けることはできません。

array<T, N> {{初期化リスト}};

整形式です。std::array残念なことに、これは のブレースなしの一時的な省略を初期化する保証された方法がないという状況につながりT x { a };、奇妙な例 (1、3、5、7) が機能する必要がないことも意味します。


最終的に を初期化するこれらの方法はすべて、std::array集計の初期化につながります。これは、集合メンバーのコピー初期化として定義されます。ただし、ブレース初期化リストを使用したコピー初期化では、集約メンバーを直接初期化できます。例えば:

struct foo { foo(int); foo(foo const&)=delete; };
std::array<foo, 2> arr0 = {1, 2};      // error: deleted copy-ctor
std::array<foo, 2> arr1 = {{1}, {2}};  // error/ill-formed, cannot initialize a
                                       // possible member array from {1}
                                       // (and too many initializers)
std::array<foo, 2> arr2 = {{{1}, {2}}}; // not guaranteed to work

1 つ目は、initializer-clauses1およびから配列要素を2それぞれ初期化しようとします。このコピー初期化は、どちらが違法であるかfoo arr0_0 = 1;と同等foo arr0_0 = foo(1);です (削除された copy-ctor)。

2 番目には式のリストが含まれていませんが、初期化子のリストが含まれているため、[array.overview]/2 の要件を満たしていません。実際にstd::arrayは、最初の initializer-clause から (のみ) 初期化される raw 配列データ メンバーが含まれています{1}。2 番目の句{2}は不正です。

3 番目には 2 番目とは逆の問題があります。配列データ メンバーがある場合は機能しますが、それは保証されません。

于 2013-12-11T13:40:41.417 に答える
1

この回答はバグレポートにリンクしており、使用時にデフォルトで有効で-Wmissing-bracesはなくなりました-Wall。をオンにすると-Wmissing-bracesgccは 0、2、4、および 6 について文句を言います ( と同じclang)。

中括弧の省略は、フォーム内のステートメントでは許可されますが、許可されT a = { ... }ませんT a { }

std::vector と std::array の C++ initializer_list の動作が異なるのはなぜですか?

James McNellisの回答は次のとおりです。

ただし、これらの追加の中括弧は、「T x = { a }; の形式の宣言」でのみ省略できます。(C++11 §8.5.1/11)、つまり、古いスタイル = が使用されている場合。中括弧の省略を許可するこの規則は、直接リストの初期化には適用されません。ここの脚注には次のように書かれています。

この制限に関する不具合報告があります: CWG不具合 #1270。提案された解決策が採用された場合、他の形式のリストの初期化でブレースの省略が許可されます...

提案された解決策が採用された場合、他の形式のリストの初期化でブレースの省略が許可され、次のものが整形式になります: std::array y{ 1, 2, 3, 4 };

そしてXeoの答え:

... std::array にはコンストラクターがなく、{1, 2, 3, 4} の中かっこで囲まれた init-list は、実際には std::initializer_list として解釈されませんが、std の内部 C スタイル配列の集合初期化です。 ::array (中かっこの 2 番目のセットの由来: 1 つは std::array 用、もう 1 つは内部の C スタイル メンバー配列用)。

std::arrayを取るコンストラクタはありませんinitializer_list。このため、集計の初期化として扱われます。もしそうなら、それは次のようになります:

#include <array>
#include <initializer_list>

struct test {
    int inner[3];

    test(std::initializer_list<int> list) {
        std::copy(list.begin(), list.end(), inner);
    }
};

#include <iostream>

int main() { 
    test t{1, 2, 3};
    test t2({1, 2, 3});
    test t3 = {1, 2, 3};
    test t4 = test({1, 2, 3});
    for (int i = 0; i < 3; i++)
        std::cout << t.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t2.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t3.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t4.inner[i];
}
于 2013-12-11T14:05:40.877 に答える