13

次のコードを検討してください。

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&) = delete;
  A(A&&) = delete;
  A(const std::string &a) : s(a) {}
};

ここで、リストの初期化を使用して A の配列を初期化したいと思います。g++ (4.9.1) は、次のコードを正常にビルドできました。

int main() {
  A arr[2] = {{"a"}, {"b"}};
  return 0;
}

ただし、次のコードでは失敗しました。

class Aggr {
private:
  A arr[2];
public:
  Aggr() : arr{{"a"}, {"b"}} {}
};

エラーメッセージは、

test.cc: In constructor ‘Aggr::Aggr()’:
test.cc:22:28: error: use of deleted function ‘A::A(A&&)’
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^          
test.cc:11:3: note: declared here
   A(A&&) = delete;
   ^

つまり、リスト初期化子は、クラス内の配列を初期化するために移動コンストラクターを呼び出そうとします。ただし、そのコードは、clang v3.5 によって警告なしで正常にビルドされました。ですから、C++11 (またはそれ以降のバージョン) がリストの初期化に関してどのような規則を指定しているかを知りたいです。前もって感謝します。

4

1 に答える 1

2

標準を何度も読んで、これはバグだと思います。

標準は何と言っていますか?

8.5.1/2 : 8.5.4 で指定されているように、集合体が初期化子リストによって初期化される場合、初期化子リストの要素は、添え字またはメンバーの昇順で、集合体のメンバーの初期化子として取得されます。各メンバーは、対応する初期化句からコピー初期化されます。

次のように説明されています。

8.5/14 : (...) はコピー初期化と呼ばれます。[ 注: コピーの初期化は、移動 (12.8) を呼び出す場合があります。—終わりのメモ]

しかし、あなたの特定のケースで移動が必要になるという証拠は12.8には見つかりませんでした。

8.5.4/3それ以外の場合、T がクラス型の場合、コンストラクターが考慮されます。T に初期化子リスト コンストラクターがある場合、引数リストは初期化子リストを 1 つの引数として構成されます。それ以外の場合、引数リストは初期化子リストの要素で構成されます。適用可能なコンストラクターが列挙され、オーバーロードの解決 (13.3) によって最適なコンストラクターが選択されます。

したがって、原則として、コードは機能するはずです。

バグですか?実験的な方法を試す

暗黙的なムーブ コンストラクターの利点を活かすため、ムーブ コンストラクターの削除をコメント アウトしました。奇妙なことに、次のエラーメッセージが表示されました。

    Compilation error   time: 0 memory: 3232 signal:0

prog.cpp: In constructor 'Aggr::Aggr()':
prog.cpp:19:28: error: use of deleted function 'A::A(const A&)'
   Aggr() : arr{{"a"}, {"b"}} {}
                            ^
prog.cpp:10:3: note: declared here
   A(const A&) = delete  

だから今、彼は不足しているコピーコンストラクターについて不平を言っています!

さらに奇妙なことに、暗黙的なものではなく、独自の移動コンストラクターを提供しました。ここでは、コードを正常にコンパイルしました!!

最後に、コピーと移動の両方を提供し、いくつかのトレースを追加しました。

class A {
private:
  std::string s;
public:
  A() = delete;
  A(const A&)  { std::cout<<"copy\n";} //= delete;
  A(A&&) { std::cout<<"move\n";} //= delete;
  A(const std::string &a) : s(a) {  std::cout<<"string ctor\n";}
};

オブジェクトを作成すると、次のAggrように表示されます。

string ctor 
string ctor

予想どおり、コピー省略を使用して配列メンバーが文字列コンストラクターから初期化されていることを示しています。

これらのテストはすべて、C++14 オプションを使用して、ideone で gcc-9.4.2 を使用して実行されました。

結論

同じコードが暗黙の move ctor でコンパイルに失敗し、ユーザー定義の move ctor で成功するという事実は、非常に重大なバグのように見えます。

move コンストラクターが使用可能なときに使用されないという事実は、この印象を強めます。

そのため、このバグを報告しました。

于 2015-01-31T15:27:46.720 に答える