Инициализирующий тип агрегата члена без конструктора копирования - PullRequest
1 голос
/ 09 марта 2020

Мне нужно инициализировать массив членов класса с помощью конструктора не по умолчанию и без использования конструктора копирования.

У меня есть два следующих класса:

class MemberClass
{
  public:
    MemberClass(int id) {  /* Do stuff */ }; // Define non-default ctor
    MemberClass(const MemberClass& other) = delete; // Delete copy ctor
    ~MemberClass() { /* Do stuff */ }; // Overide default dtor
};

class ContainerClass
{
  private:
    MemberClass mem[2];

  public:
    ContainerClass(int id)
        : mem { {id} , {id} }
          {}
};

, которые после Компиляция выдает следующую ошибку:

error: use of deleted function ‘MemberClass::MemberClass(const MemberClass&)’
         : mem { {id} , {id} }

, но я не могу найти способ инициализировать массив mem без определения конструктора копирования. Я нашел ответы здесь и здесь , объясняющие, что происходит исключение копирования, и для компиляции необходим copy-ctor, но его должен удалить компилятор. MemberClass никогда не следует копировать, поэтому определение copy-ctor только для этой инициализации кажется очень неуклюжим и склонным к более сложной отладке в других местах.

Если MemberClass имеет только конструктор по умолчанию, тогда нет вопрос, заданный компилятором. Также не возникает никаких проблем, если mem не является массивом и является просто одним MemberClass объектом. Моя единственная проблема заключается в инициализации этого массива с использованием ctor не по умолчанию и без использования copy-ctor.

Странно, если я не определяю деструктор, я не получаю никакой ошибки компиляции, которая выглядит как подсказка.

Есть ли "правильный" способ выполнить такую ​​инициализацию?

1 Ответ

0 голосов
/ 10 марта 2020

Я думаю, что это ошибка в g cc.

Как указано в C ++ 17 [dcl.init.aggr] / 3 (этот текст был практически таким же в C ++ 14) :

Когда агрегат инициализируется списком инициализаторов, как указано в 11.6.4, элементы списка инициализаторов берутся в качестве инициализаторов для элементов агрегата по порядку. Каждый элемент инициализируется копией из соответствующего предложения инициализатора . [...] Если предложение инициализатора само является списком инициализатора, элемент инициализируется списком,

Я проанализирую три различных случая здесь:

  • Дело 1: mem { id, id }
  • Дело 2: mem { MemberClass{id}, MemberClass{id} }
  • Дело 3: mem { {id}, {id} }

В деле 1, mem[0] инициализируется копией выражением id. Это тот же тип инициализации, что и MemberClass x = id;. Он описан в dcl.init / 17.6.2 (инициализация копирования из выражения другого типа).

Поведение заключается в том, что инициализатор преобразуется в значение типа (то есть MemberClass{id}), которое затем направляет инициализирует цель. В C ++ 14 это было неправильно: несмотря на то, что это контекст копирования, действительный конструктор копирования все еще должен существовать. В C ++ 17 это правильно: инициализация объекта из prvalue того же типа аналогична инициализации объекта с использованием конструктора, указанного в prvalue (так называемое «гарантированное разрешение копирования»).

Случай 2 аналогичен случаю 1: в нем используется 17.6.1 (инициализация из выражения того же типа), и применяется тот же анализ для случая 1.

Однако, случай 3 отличается. В соответствии с последней жирной цитатой из dcl.init.aggr / 3, mem[0] инициализируется списком на {id}, то есть код должен вести себя так же, как MemberClass z {id};. Нет временной операции или операции копирования даже в C ++ 14.

Поэтому правильное поведение:

  • C ++ 14 - Случай 3 верный, Случай 1 и 2 плохой -formed.
  • C ++ 17 - Все случаи правильны.

Сообщения об ошибках, выдаваемые g cc, предполагают, что он в режиме C ++ 14 обрабатывает Случай 3, ваш код, такой же, как и в двух других случаях. А в режиме C ++ 17 он никогда не получал памятку о гарантированном разрешении копирования.

...