Подтвердить что ты не робот

Список инициализаторов на языке С++ 11 с инициализатором в классе?

Какая разница между этими способами инициализации переменных-членов объекта в С++ 11? Есть ли другой способ? какой способ лучше (производительность)?:

class any {
  public:
    obj s = obj("value");
    any(){}
};

Или

class any {
  public:
    obj s;
    any(): s("value"){}
};

Спасибо.

4b9b3361

Ответ 1

Нет, это не то же самое.

Разница между ними та же, что применяется для прямой инициализации и для инициализации копирования, которая является тонкой, но часто очень запутанной.

§12.6.2 [class.base.init]:

  1. Список-список выражений или бит-init-list в mem-initializer используется для инициализации указанного подобъекта (или, в случае делегирующего конструктора, полного объекта класса) в соответствии с правилами инициализации 8.5 для прямой инициализации. [...]

  2. В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначен идентификатором mem-initializer (включая случай, когда нет списка mem-initializer, поскольку конструктор не имеет ctor-initializer), и сущность не является виртуальным базовым классом абстрактного класса (10.4), то

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

§8.5 [dcl.init]:

  1. Инициализация, которая встречается в форме

    T x = a;

а также при передаче аргументов, возврату функции, выделении исключения (15.1), обработке исключения (15.3) и инициализации элемента агрегации (8.5.1) называется копирование-инициализация.

Инициализация нестатического элемента данных в списке-initializer-list соответствует правилам прямой инициализации, которые не создают промежуточные временные файлы, которые необходимо переместить/скопировать (если они скомпилированы без копирования-элиты), ни тип элемента данных должен быть с возможностью копирования/перемещения (даже если копия завершена). Кроме того, прямая инициализация вводит явный контекст, в то время как инициализация копирования не явная (если конструктор, выбранный для инициализации, равен explicit, программа не будет компилироваться).

Другими словами, синтаксис obj s = obj("value"); не будет компилироваться, если obj объявлен как:

struct obj
{
    obj(std::string) {}
    obj(const obj&) = delete;
};

или

struct obj
{
    obj(std::string) {}
    explicit obj(const obj&) {}
};

В качестве более ощутимого примера, в то время как ниже не будет компилироваться:

struct any
{
   std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
   std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};

это будет:

struct any
{
    std::atomic<int> a;
    std::atomic<int> b{ 2 };
    any() : a(1) {}
};

Какой способ лучше (производительность)?

С включенным копированием, обе имеют идентичную производительность. Если отключено копирование, есть дополнительный вызов конструктора копирования/перемещения при каждом экземпляре, когда используется синтаксис копирования-инициализации (который obj s = obj("value"); является одним из).


Есть ли другой способ?

Синтаксис подстановки или выравнивания-инициализатора также позволяет выполнить инициализацию прямого списка:

class any {
public:
    obj s{ "value" };
    any() {}
};

Есть ли другие отличия?

Некоторые другие отличия, которые стоит упомянуть, следующие:

  • Элемент-бит-или-равный-инициализатор должен находиться в файле заголовка вместе с объявлением класса.
  • Если оба они объединены, список-инициализатор-член имеет приоритет по сравнению с принципом привязки-равным-инициализатором (то есть, игнорируется элемент выравнивания с равным или равным).
  • (только для С++ 11, до С++ 14) Класс, который использует механизм скрепления или равный-инициализатор, нарушает ограничения для агрегатного типа.
  • С синтаксисом brace-or-equal-initializer невозможно выполнить прямую инициализацию, отличную от инициализации прямого списка.

Ответ 2

Оба примера эквивалентны. Хотя только если тип скопирован или движимый (проверьте его для себя), и NRVO на самом деле сделан (любой полупристойный компилятор сделает это, как само собой разумеющееся).

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

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

Ответ 3

Они одинаковы.

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

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

Из стандарта:

12.6.2.5
nonstatic data members shall be initialized in the order they were declared 
in the class definition

Если вы ошибаетесь в своем списке, GCC будет жаловаться:

main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning:   'int C::a'

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