Удаленный конструктор по умолчанию. Объекты все еще могут быть созданы... иногда
Наивный, оптимистичный и 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 другими способами.