Удаленный конструктор по умолчанию. Объекты все еще могут быть созданы... иногда

Наивный, оптимистичный и oh.. так неправильный взгляд на синтаксис синтаксиса С++ 11

Я думал, что поскольку С++ 11 пользовательские объекты типа должны быть построены с помощью нового синтаксиса {...} вместо старого синтаксиса (...) (за исключением перегруженного конструктора для std::initializer_list и аналогичных параметров (например, std::vector: размер ctor vs 1 elem init_list ctor)).

Преимущества: отсутствие узких неявных преобразований, отсутствие проблем с наиболее неприятным анализом, согласованность (?). Я не видел проблем, поскольку я думал, что они одинаковые (кроме приведенного примера).

Но это не так.

Рассказ о безумном безумии

{} вызывает конструктор по умолчанию.

... За исключением случаев, когда:

  • конструктор по умолчанию удаляется и
  • других конструкторов не определено.

Тогда похоже, что это скорее значение инициализирует объект?... Даже если объект удалил конструктор по умолчанию, {} может создать объект. Разве это не превзошло всю цель удаленного конструктора?

... За исключением случаев, когда:

  • объект имеет удаленный конструктор по умолчанию и
  • определены другие конструкторы.

Затем он терпит неудачу с call to deleted constructor.

... За исключением случаев, когда:

  • объект имеет удаленный конструктор и
  • никакой другой конструктор не определен и
  • как минимум нестатический элемент данных.

Затем он не работает с отсутствующими инициализаторами поля.

Но тогда вы можете использовать {value} для создания объекта.

Хорошо, возможно, это то же самое, что и первое исключение (значение init объект)

... За исключением случаев, когда:

  • класс имеет удаленный конструктор
  • и по крайней мере один элемент данных в классе по умолчанию инициализирован.

Тогда ни {}, ни {value} не могут создать объект.

Я уверен, что пропустил несколько. Ирония заключается в том, что он называется равномерным синтаксисом инициализации. Я снова говорю: синтаксис инициализации UNIFORM.

Что это за безумие?

Сценарий A

Удаленный конструктор по умолчанию:

struct foo {
  foo() = delete;
};

// All bellow OK (no errors, no warnings)
foo f = foo{};
foo f = {};
foo f{}; // will use only this from now on.

Сценарий B

Удаленный конструктор по умолчанию, остальные конструкторы удалены

struct foo {
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // OK

Сценарий C

Удаленный конструктор по умолчанию, другие конструкторы, определенные

struct foo {
  foo() = delete;
  foo(int) {};
};

foo f{}; // error call to deleted constructor

Сценарий D

Удаленный конструктор по умолчанию, другие конструкторы не определены, член данных

struct foo {
  int a;
  foo() = delete;
};

foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK

Сценарий E

Удаленный конструктор по умолчанию, удаленный конструктор T, член данных T

struct foo {
  int a;
  foo() = delete;
  foo(int) = delete;
};

foo f{}; // ERROR: missing initializer
foo f{3}; // OK

Сценарий F

Удаленный конструктор по умолчанию, инициализаторы элементов данных класса

struct foo {
  int a = 3;
  foo() = delete;
};

/* Fa */ foo f{}; // ERROR: use of deleted function `foo::foo()`
/* Fb */ foo f{3}; // ERROR: no matching function to call `foo::foo(init list)`

Ответ 1

При просмотре вещей таким образом легко сказать, что существует полный и полный хаос в способе инициализации объекта.

Большое различие происходит от типа foo: если это тип агрегата или нет.

Это совокупность, если она имеет:

  • нет созданных пользователем конструкторов (удаленная или дефолтная функция не считается предоставленной пользователем),
  • нет частных или защищенных нестатических членов данных,
  • нет элементарных элементов для нестатических данных (поскольку С++ 11 до (вернувшийся в) С++ 14)
  • нет базовых классов,
  • нет виртуальных функций-членов.

Итак:

  • в сценариях A B D E: foo - это совокупность
  • в сценариях C: foo не является агрегированным
  • сценарий F:
    • в С++ 11 это не совокупность.
    • в С++ 14 это совокупность.
    • g++ не реализовал это и по-прежнему рассматривает его как неагрегат даже в С++ 14.
      • 4.9 не реализует это.
      • 5.2.0 делает
      • 5.2.1 ubuntu не (возможно, регрессия)

Эффекты инициализации списка объекта типа T:

  • ...
  • Если T является агрегированным типом, выполняется агрегатная инициализация. Это касается сценариев A B D E (и F в С++ 14)
  • В противном случае конструкторы T рассматриваются в две фазы:
    • Все конструкторы, которые принимают std:: initializer_list...
    • в противном случае [...] все конструкторы T участвуют в разрешении перегрузки [...] Это касается C (и F в С++ 11)
  • ...

:

Совокупная инициализация объекта типа T (сценарии A B D E (F С++ 14)):

  • Каждый нестатический член класса, при появлении внешнего вида в определении класса, инициализируется с копией из соответствующего раздела список инициализаторов. (ссылка массива опущена)

TL; DR

Все эти правила все еще могут казаться очень сложными и вызывающими головную боль. Я лично чрезмерно упрощаю это для себя (если я тем самым застрелю себя в ногу, то и так: я думаю, я проведу 2 дня в больнице, а не на несколько десятков дней головных болей):

  • для совокупности каждый элемент данных инициализируется из элементов инициализатора списка
  • else конструктор вызовов

Разве это не превзошло всю цель удаленного конструктора?

Ну, я не знаю об этом, но решение состоит в том, чтобы сделать foo не совокупностью. Самая общая форма, которая не добавляет лишних затрат и не изменяет используемый синтаксис объекта, заключается в том, чтобы наследовать его от пустой структуры:

struct dummy_t {};

struct foo : dummy_t {
  foo() = delete;
};

foo f{}; // ERROR call to deleted constructor

В некоторых ситуациях (как я полагаю, нет нестатических элементов) альтернативой будет удаление деструктора (это сделает объект несовместимым в любом контексте):

struct foo {
  ~foo() = delete;
};

foo f{}; // ERROR use of deleted function `foo::~foo()`

В этом ответе используется информация, полученная из:

Большое спасибо @M.M, которые помогли исправить и улучшить этот пост.

Ответ 2

Что заставляет вас запускать агрегатную инициализацию.

Как вы говорите, есть преимущества и недостатки в использовании инициализации списка. (Термин "равномерная инициализация" не используется стандартом С++).

Одним из недостатков является то, что инициализация списка ведет себя по-разному для агрегатов, чем неагрегаты. Кроме того, определение совокупности немного меняется с каждым стандартом.


Агрегаты не создаются с помощью конструктора. (Технически они на самом деле могут быть, но это хороший способ подумать об этом). Вместо этого при создании агрегата выделяется память, а затем каждый элемент инициализируется в порядке, в соответствии с тем, что в инициализаторе списка.

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

На самом деле есть недостаток дизайна: если у нас T t1; T t2{t1};, то целью является выполнение copy-construction. Однако (до С++ 14), если T является агрегатом, тогда вместо этого выполняется инициализация агрегата, а t2 первый элемент инициализируется с помощью t1.

Этот недостаток был зафиксирован в отчете дефекта, который изменил С++ 14, поэтому с этого момента проверяется копирование, прежде чем мы перейдем к агрегатной инициализации.


Определение агрегата из С++ 14:

Агрегат - это массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без частных или защищенных нестатических элементов данных (раздел 11), без базовых классов (раздел 10) и без виртуальных функции (10.3).

В С++ 11 значение по умолчанию для нестатического элемента означает, что класс не является агрегатом; однако это было изменено для С++ 14. Пользовательские средства означают объявленные пользователем, но не = default или = delete.


Если вы хотите, чтобы ваш вызов конструктора никогда не выполнял агрегатную инициализацию, вы должны использовать ( ), а не { }, и избегать MVP другими способами.