Удаленный конструктор по умолчанию. Объекты все еще могут быть созданы … иногда
Наивный, оптимистичный и о … неправильный взгляд на синтаксический синтаксис c ++ 11
Я думал, что поскольку C ++ 11 пользовательские объекты типа должны быть сконструированы с новым синтаксисом {...}
вместо старого (...)
синтаксиса (за исключением перегруженного конструктора для std::initializer_list
и подобных параметров (например, std::vector
: размер ctor vs 1 elem init_list ctor)).
Преимущества: отсутствие узких неявных преобразований, отсутствие проблем с наиболее неприятным анализом, согласованностью (?). Я не видел проблем, поскольку я думал, что они одинаковые (за исключением приведенного примера).
Но это не так.
- Возьмите адрес элемента массива «один конец прошлого» через индекс: легальный по стандарту C ++ или нет?
- Что говорит стандарт о том, как определение четкости вектора меняет емкость?
- Является ли stl-вектор одновременным чтением streamобезопасным?
- В C ++ 11, имеет ли `i + = ++ i + 1` неопределенное поведение?
- Включает ли инициализация преобразование lvalue-to-rvalue? Является ли `int x = x;` UB?
Сказка о безумном безумии
{}
Вызывает конструктор по умолчанию.
… Кроме того, когда:
- конструктор по умолчанию удаляется и
- нет других конструкторов.
Тогда похоже, что это скорее значение инициализирует объект? … Даже если объект удалил конструктор по умолчанию, {}
может создать объект. Разве это не превзошло всю цель удаленного конструктора?
… Кроме того, когда:
- объект имеет удаленный конструктор по умолчанию и
- другой конструктор (ы), определенный.
Затем он терпит неудачу с 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)`
- Это действительно нарушает правила строгого сглаживания?
- Указывает ли объявление, использующее «авто», выражение extern, использующее спецификатор конкретного типа?
- Псевдонимы T * с char * разрешены. Разрешено ли это другим способом?
- int a = {1,2,}; Разрешена странная запятая. Любая конкретная причина?
- Чистые виртуальные функции могут не иметь встроенного определения. Зачем?
- Является ли std :: abs (0u) плохо сформированным?
- Доступ к атрибутам на литералах работает на всех типах, но не `int`; Зачем?
- Одномерный доступ к многомерному массиву: хорошо ли оно определено?
При просмотре вещей таким образом легко сказать, что существует полный и полный хаос в способе инициализации объекта.
Большое различие происходит от типа 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 вперед.