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

Я думал, что, поскольку 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

Сценарий 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)`

47

Решение

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

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

Это агрегат, если он имеет:

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

Так:

  • в сценариях A B D E: foo это совокупность
  • в сценариях C: foo это не совокупность
  • сценарий F:
    • в с ++ 11 это не агрегат.
    • в с ++ 14 это агрегат.
    • g ++ не реализовал это и все еще рассматривает это как неагрегированное даже в C ++ 14.
      • 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 кто помог исправить и улучшить этот пост.

33

Другие решения

Что тебя портит агрегатная инициализация.

Как вы говорите, у инициализации списка есть свои преимущества и недостатки. (Термин «равномерная инициализация» не используется стандартом 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 другими способами.

7

Эти случаи, связанные с общей инициализацией, для большинства являются нелогичными и были предметом предложения. 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 вперед.

4
По вопросам рекламы [email protected]