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

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

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

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

Но это не так.

Сказка о безумном безумии

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

… Кроме того, когда:

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

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

… Кроме того, когда:

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

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

… Кроме того, когда:

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

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

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

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

… Кроме того, когда:

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

Тогда ни {} ни {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

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

 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)` 

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

Большое различие происходит от типа foo : если он является совокупным типом или нет.

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

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

Так:

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

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

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

:

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

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

TL; DR

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

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

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

Ну, я не знаю об этом, но решение состоит в том, чтобы сделать 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()` 

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

  • Инициализация значения C ++ 14 с удаленным конструктором

  • Что такое Агрегаты и POD и как / почему они являются особенными?

  • Инициализация списка

  • Совокупная инициализация
  • Прямая инициализация

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

Что вам нужно, это агрегатная инициализация .

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

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


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

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

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

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


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

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

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


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

Эти случаи вокруг агрегатной инициализации для большинства являются интуитивно понятными и были предметом предложения p1008: Запретить агрегаты с объявленными пользователем конструкторами, которые гласят:

В настоящее время C ++ позволяет инициализировать некоторые типы с объявленными пользователем конструкторами через агрегатную инициализацию, минуя эти конструкторы. Результатом является код, который является удивительным, запутанным и ошибочным. В этом документе предлагается исправление, которое делает семантику инициализации в C ++ более безопасным, более однородным и более простым для обучения. Мы также обсудим изменения, внесенные в это исправление

и вводит некоторые примеры, которые хорошо сочетаются с представленными вами случаями:

 struct X { X() = delete; }; int main() { X x1; // ill-formed - default c'tor is deleted X x2{}; // compiles! } 

Очевидно, что цель удалённого конструктора состоит в том, чтобы запретить пользователю инициализировать class. Однако, вопреки интуиции, это не работает: пользователь все равно может инициализировать X посредством инициализации агрегата, потому что это полностью обходит конструкторы. Автор может даже явно удалить все конструкторы по умолчанию, копировать и перемещать, и все равно не может предотвратить код клиента от создания экземпляра X посредством инициализации агрегата, как указано выше. Большинство разработчиков на C ++ удивлены текущим поведением, когда показан этот код. Автор classа X может альтернативно рассмотреть вопрос о том, чтобы сделать конструктор по умолчанию закрытым. Но если этому конструктору задано дефолтное определение, это снова не предотвращает инициализацию агрегата (и, следовательно, экземпляр) classа:

 struct X { private: X() = default; }; int main() { X x1; // ill-formed - default c'tor is private X x2{}; // compiles! } 

Из-за существующих правил агрегатная инициализация позволяет нам «строить по умолчанию» class, даже если он не является, по сути, конструктивным:

  static_assert(!std::is_default_constructible_v); 

будет передаваться для обоих определений X выше.

Предлагаемые изменения:

Измените пункт [dcl.init.aggr] 1 следующим образом:

Агрегатом является массив или class (раздел 12) с

  • без пользовательских, явных , объявленных пользователем или унаследованных конструкторов (15.1),

  • нет частных или защищенных нестатических данных (раздел 14),

  • нет виртуальных функций (13.3) и

  • нет виртуальных, частных или защищенных базовых classов (13.1).

Измените пункт [dcl.init.aggr] 17 следующим образом:

[Примечание: совокупный массив или совокупный class могут содержать элементы типа class >> с предоставленный пользователем объявленный пользователем конструктор (15.1). Инициализация >> этих совокупных объектов описана в 15.6.1. -End note]

Добавьте в [diff.cpp17] следующее приложение C, раздел C.5 C ++ и ISO C ++ 2017:

C.5.6 Статья 11: деклараторы [diff.cpp17.dcl.decl]

Затронутый подпункт : [dcl.init.aggr]
Изменить : class, который имеет объявленные пользователем конструкторы, никогда не является агрегированным.
Обоснование : удалить потенциально опасную агрегатную инициализацию, которая может применяться не к объявленным конструкторам classа.
Влияние на исходную функцию : Действительный код C ++ 2017, который агрегирует-инициализирует тип с объявленным пользователем конструктором, может быть плохо сформированным или иметь разную семантику в этом Международном стандарте.

Далее следуют примеры, которые я опускаю.

Предложение было принято и объединено с C ++ 20, мы можем найти последний проект здесь, который содержит эти изменения, и мы можем увидеть изменения в [dcl.init.aggr] p1.1 и [dcl.init.aggr] p17 и C ++ 17 деклараций diff .

Поэтому это должно быть исправлено в C ++ 20 вперед.

  • Когда это действительно для доступа к указателю на «мертвый» объект?
  • Имеет ли printf ("% x", 1) неопределенное поведение?
  • Используя std :: bind с функцией-членом, используйте указатель объекта или нет для этого аргумента?
  • "Inline" ключевое слово vs "inlining" concept
  • Вычисления без последствий (ака последовательности)
  • При использовании заголовков C в C ++ следует ли использовать функции из std :: или глобального пространства имен?
  • Является ли сдвиг слева (<<) отрицательным целым неопределенным поведением в C ++ 11?
  • Эффективный беззнаковый кран, исключающий поведение, определяемое реализацией
  • «Создание» объекта с возможностью копирования с возможностью memcpy
  • Interesting Posts

    Нет подключения к Интернету в Android-x86 4.2 ISO на VirtualBox

    Как найти аннотированные методы в данном пакете?

    Каково обоснование для всех сравнений, возвращающих false для значений NaN IEEE754?

    Вентилятор ноутбука должен начинать и останавливаться очень часто?

    Как заполнить JComboBox из текстового файла?

    Не удается установить .NET Framework 3.5 на Windows 10

    Неразрешенные символы при связывании программы с использованием libcurl

    Android и получение представления с литой идентификатором в виде строки

    Как читать файл Doc или Docx в java?

    Изменение цвета текста в WinForms RichTextBox

    Должен ли я установить Windows 8 Developer Preview поверх Windows 7?

    Как подключить Western Digital «Мой паспорт» с помощью других средств?

    Спиральное правило и «объявление следует за использованием» для синтаксического анализа объявлений C и C ++

    Изменение файла hosts на снежном барсе

    Фоновая работа в bash отключается, когда окно запуска закрыто: как этого избежать?

    Давайте будем гением компьютера.