Я думал, что, поскольку C ++ 11 объекты пользовательских типов должны быть построены с новым {...}
синтаксис вместо старого (...)
синтаксис (за исключением конструктора, перегруженного для std::initializer_list
и аналогичные параметры (например, std::vector
: размер ctor против 1 элемента init_list ctor)).
Преимущества: нет узких неявных преобразований, нет проблем с самым неприятным синтаксическим анализом, согласованность (?). Я не видел проблем, так как думал, что они одинаковы (кроме приведенного примера).
Но это не так.
{}
вызывает конструктор по умолчанию
… Кроме случаев, когда:
Тогда похоже, что это скорее значение инициализирует объект? … Даже если объект удалил конструктор по умолчанию, {}
может создать объект. Разве это не превосходит всю цель удаленного конструктора?
…Кроме случаев, когда:
Тогда это не с call to deleted constructor
,
…Кроме случаев, когда:
Тогда это терпит неудачу с отсутствующими инициализаторами поля.
Но тогда вы можете использовать {value}
построить объект.
Хорошо, возможно, это то же самое, что и первое исключение (значение init the object)
…Кроме случаев, когда:
Тогда ни {}
ни {value}
может создать объект.
Я уверен, что я пропустил несколько. Ирония в том, что это называется единообразный синтаксис инициализации. Я снова говорю: ЕДИНАЯ синтаксис инициализации.
Что это за безумие?
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.
struct foo {
foo() = delete;
foo(int) = delete;
};
foo f{}; // OK
struct foo {
foo() = delete;
foo(int) {};
};
foo f{}; // error call to deleted constructor
struct foo {
int a;
foo() = delete;
};
foo f{}; // error use of deleted function foo::foo()
foo f{3}; // OK
struct foo {
int a;
foo() = delete;
foo(int) = delete;
};
foo f{}; // ERROR: missing initializer
foo f{3}; // OK
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)
- нет базовых классов,
- нет виртуальных функций-членов.
Так:
foo
это совокупностьfoo
это не совокупность4.9
не реализует это.5.2.0
делает5.2.1 ubuntu
нет (возможно, регрессия)Эффекты инициализации списка объекта типа T:
- …
- Если T является агрегатным типом, выполняется агрегатная инициализация. Это заботится о сценариях A B D E (и F в C ++ 14)
- В противном случае конструкторы T рассматриваются в два этапа:
- Все конструкторы, которые принимают std :: initializer_list …
- в противном случае […] все конструкторы T участвуют в разрешении перегрузки […] Это заботится о C (и F в C ++ 11)
- …
:
Совокупная инициализация объекта типа T (сценарии A B D E (F c ++ 14)):
- Каждый нестатический член класса, в порядке появления в определении класса, инициализируется копией из соответствующего предложения
список инициализаторов. (ссылка на массив опущена)
TL; DR
Все эти правила все еще могут казаться очень сложными и вызывать головную боль. Я лично слишком упрощаю это для себя (если я застрелюсь в ногу, пусть будет так: я думаю, я проведу 2 дня в больнице, а не пару десятков дней головных болей):
Разве это не превосходит всю цель удаленного конструктора?
Ну, я не знаю об этом, но решение состоит в том, чтобы сделать 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 кто помог исправить и улучшить этот пост.
Что тебя портит агрегатная инициализация.
Как вы говорите, у инициализации списка есть свои преимущества и недостатки. (Термин «равномерная инициализация» не используется стандартом C ++).
Одним из недостатков является то, что инициализация списка ведет себя иначе для агрегатов, чем для неагрегатов. Кроме того, определение совокупный слегка меняется с каждым стандартом.
Агрегаты не создаются с помощью конструктора. (Технически они действительно могут быть, но это хороший способ думать об этом). Вместо этого при создании агрегата выделяется память, а затем каждый элемент инициализируется в порядке, соответствующем тому, что находится в инициализаторе списка.
Неагрегаты создаются с помощью конструкторов, и в этом случае члены инициализатора списка являются аргументами конструктора.
На самом деле есть недостаток дизайна в приведенном выше: если мы имеем T t1; T t2{t1};
, тогда цель состоит в том, чтобы выполнить копирование-конструирование. Однако (до C ++ 14), если T
является агрегатом, то вместо этого происходит инициализация агрегата, и t2
Первый член инициализируется с t1
,
Этот недостаток был исправлен в отчет о дефектах который изменил C ++ 14, так что теперь проверяется конструкция копирования, прежде чем мы перейдем к инициализации агрегата.
Определение совокупный из C ++ 14 есть:
Агрегат — это массив или класс (раздел 9) без предоставленных пользователем конструкторов (12.1), без закрытых или защищенных нестатических элементов данных (пункт 11), без базовых классов (пункт 10) и без виртуальных функций (10.3 ).
В C ++ 11 значение по умолчанию для нестатического члена означало, что класс не был агрегатом; однако это было изменено для C ++ 14. Предоставленный пользователем означает, что пользователь объявлен, но не = default
или же = delete
,
Если вы хотите убедиться, что ваш конструктор вызывает никогда случайно выполняет агрегатную инициализацию, тогда вы должны использовать ( )
скорее, чем { }
и избегать MVP другими способами.
Эти случаи, связанные с общей инициализацией, для большинства являются нелогичными и были предметом предложения. p1008: Запретить агрегаты с помощью объявленных пользователем конструкторов который говорит:
C ++ в настоящее время позволяет инициализировать некоторые типы с объявленными пользователем конструкторами через агрегат
инициализация, минуя эти конструкторы. В результате получается код, который удивителен, запутан и
багги. В этой статье предлагается исправление, которое делает семантику инициализации в C ++ более безопасной, более унифицированной,
и легче учить. Мы также обсуждаем критические изменения, которые вносит это исправление
и представляет некоторые примеры, которые хорошо совпадают с представленными вами случаями:
struct X { X() = delete; }; int main() { X x1; // ill-formed - default c’tor is deleted X x2{}; // compiles! }
Понятно, что цель удаленного конструктора — не дать пользователю инициализировать класс. Однако, вопреки интуиции, это не работает: пользователь все еще может инициализировать X
через агрегатную инициализацию, потому что это полностью обходит конструкторы. Автор может даже явно удалить весь конструктор по умолчанию, копировать и переместить и все же не сможет помешать клиентскому коду создать экземпляр X через агрегатную инициализацию, как описано выше. Большинство разработчиков C ++ удивлены
текущее поведение при показе этого кода
Автор класса X может альтернативно рассмотреть возможность создания конструктора по умолчанию
частный. Но если
этому конструктору дано определение по умолчанию, это опять-таки не предотвращает агрегатную инициализацию (и, следовательно, создание экземпляров) класса:struct X { private: X() = default; }; int main() { X x1; // ill-formed - default c’tor is private X x2{}; // compiles! }
Из-за текущих правил, агрегатная инициализация позволяет нам «конструировать по умолчанию» класс, даже если он на самом деле не конструируемый по умолчанию:
static_assert(!std::is_default_constructible_v<X>);
будет проходить для обоих определений X выше.
…
Предлагаемые изменения:
Изменить [dcl.init.aggr] пункт 1 следующим образом:
Агрегат — это массив или класс (раздел 12) с
не предоставлено пользователем, явное, пользователь объявлена или унаследовано
конструкторы (15.1),нет частных или защищенных нестатических элементов данных (пункт 14),
нет виртуальных функций (13.3), и
нет виртуальных, частных или защищенных базовых классов (13.1).
Изменить [dcl.init.aggr] пункт 17 следующим образом:
[Примечание: агрегатный массив или агрегатный класс могут содержать элементы класса >> типа спредоставленный пользователемпользователь объявлена конструктор (15.1). Инициализация >> этих агрегатных объектов описана в 15.6.1. —Конечная записка]Добавить следующее в [diff.cpp17] в Приложении C, раздел C.5 C ++ и ISO C ++ 2017:
C.5.6 Пункт 11: деклараторы [diff.cpp17.dcl.decl]
Затрагиваемый подпункт: [dcl.init.aggr] + Изменить: Класс, который имеет
объявленные пользователем конструкторы никогда не являются агрегатами.
обоснование: Удалить
потенциально подверженная ошибкам агрегатная инициализация, которая может применяться
не выдерживает заявленных конструкторов класса.
Влияние на оригинальную особенность: Допустимый код C ++ 2017, который агрегирует-инициализирует
Тип с объявленным пользователем конструктором может быть неправильно сформирован или иметь
различная семантика в этом международном стандарте.
Далее следуют примеры, которые я опускаю.
Предложение было приняты и объединены в C ++ 20 мы можем найти последний проект здесь который содержит эти изменения, и мы можем увидеть изменения в [Dcl.init.aggr] P1.1 а также [Dcl.init.aggr] р17 а также C ++ 17 объявлений diff.
Так что это должно быть исправлено в C ++ 20 вперед.