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

наивный, оптимистичный и ах.. так что неправильное представление о едином синтаксисе инициализации c++11

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

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

но это не так.

Сказка о чистом безумии

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

... Кроме тех случаев, когда:

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

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

...Кроме тех случаев, когда:

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

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

...Кроме тех случаев, когда:

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

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

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

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

...Кроме тех случаев, когда:

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

тогда, ни {}, ни {value} создать объект.

я уверен, что пропустил несколько. Ирония в том, что это называется униформа синтаксис инициализации. Я повторяю:униформа синтаксис инициализации.

что это за безумие?

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

3 ответов


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

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

это агрегате, если это:

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

так:

  • в сценариях A B D E:foo - это совокупность
  • в сценариях C:foo не является совокупностью
  • сценарий F:
    • в C++11 это не объединять.
    • в C++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()`

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

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


что тебя беспокоит это агрегатной инициализации.

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

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


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

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

на самом деле есть недостаток в дизайне выше: если у нас есть T t1; T t2{t1};, затем намерение для выполнения копирования работ. Однако (до C++14) if T является агрегатом, тогда вместо этого происходит агрегатная инициализация, и t2 ' s первый член инициализируется с t1.

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


определение совокупность из C++14:

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

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


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


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

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

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

struct X {
    X() = delete;
  };

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

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

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.в этом.aggr] пункт 1 следующего содержания:

An aggregate-это массив или класс (пункт 12) с

  • нет пользователей-при условии, явного, пользователей-заявил или по наследству конструкторы (15.1),

  • не private или protected нестатических членов данных (п. 14),

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

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

изменить [dcl.в этом.aggr] пункт 17 следующего содержания:

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

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

С. 5.6 п. 11: деклараторы [дифф.cpp17.dcl.decl]

пострадавших подпунктом: [dcl.в этом.примечанияа]
изменить: классе пользователей-заявил конструкторы-это не совокупность.
обоснование: удалить потенциально подверженная ошибкам агрегатная инициализация, которая может применяться не выдержав заявленного конструкторы класса.
влияние на первоначально функции: допустимый код C++ 2017, который aggregate-инициализирует тип с объявленным пользователем конструктором может быть неправильно сформирован или иметь различная семантика в этом международном стандарте.

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

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

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