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

Будет ли использоваться конструктивное поведение изменения синтаксиса brace-init при добавлении конструктора initializer_list позже?

Предположим, что у меня есть класс:

class Foo
{
public:
    Foo(int something) {}
};

И я создаю его с помощью этого синтаксиса:

Foo f{10};

Затем я добавлю новый конструктор:

class Foo
{
public:
    Foo(int something) {}
    Foo(std::initializer_list<int>) {}
};

Что происходит со строительством f? Я понимаю, что он больше не будет вызывать первый конструктор, а вместо этого вызывает конструктор списка инициализации. Если это так, это кажется плохим. Почему так много людей рекомендуют использовать синтаксис {} над () для построения объекта при добавлении конструктора initializer_list позже, может сломаться без проблем?

Я могу представить себе случай, когда я строю rvalue с использованием синтаксиса {} (чтобы избежать наиболее неприятного разбора), но потом кто-то добавляет конструктор std::initializer_list к этому объекту. Теперь код ломается, и я больше не могу его строить, используя rvalue, потому что мне нужно будет вернуться к синтаксису (), и это вызовет самый неприятный синтаксический анализ. Как бы справиться с этой ситуацией?

4b9b3361

Ответ 1

Что происходит со строительством f? Я понимаю, что он больше не будет вызывать первый конструктор, а вместо этого вызывает конструктор списка инициализации. Если это так, это кажется плохим. Почему так много людей рекомендуют использовать синтаксис {} по сравнению с () для построения объекта при добавлении конструктора initializer_list позже, может сломать вещи молча?

С одной стороны, необычно иметь конструктор списка инициализаторов, а другой - быть жизнеспособным. С другой стороны, "универсальная инициализация" получила слишком много шумихи вокруг стандартной версии С++ 11, и ее не следует использовать без вопросов.

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

Я могу представить себе случай, когда я строю rvalue с использованием синтаксиса {} (чтобы избежать наиболее неприятного анализа), но потом кто-то добавляет к этому объекту конструктор std:: initializer_list. Теперь код ломается, и я больше не могу его строить, используя rvalue, потому что мне придется переключиться на синтаксис(), и это вызовет самый неприятный синтаксический анализ. Как бы справиться с этой ситуацией?

MVP происходит только с двусмысленностью между декларатором и выражением, и это происходит только до тех пор, пока все конструкторы, которые вы пытаетесь вызвать, являются конструкторами по умолчанию. Пустой список {} всегда вызывает конструктор по умолчанию, а не конструктор списка инициализаторов с пустым списком. (Это означает, что его можно использовать без риска. "Универсальная" инициализация значений - это настоящая вещь.)

Если есть какое-либо подвыражение внутри фигурных скобок/парен, проблема MVP уже решена.

Ответ 2

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

Если бы у меня был класс, подобный вектору, который взял размер, то, возможно, использование синтаксиса {} является "неправильным", но для перехода мы все равно хотим его поймать. Построение C c1 {val} означает принятие некоторых (в этом случае) значений для коллекции, а C c2 (arg) означает использование val как описательной части метаданных для класса.

Чтобы поддерживать оба использования, когда тип элемента совместим с описательным аргументом, код, который использовал C c2 {arg}, изменит значение. Похоже, что в этом случае нет возможности обойти эту форму, если мы хотим поддерживать обе формы с разными значениями.

Итак, что бы я сделал? Если компилятор предоставляет какой-то способ для выдачи предупреждения, я бы сделал список инициализаций одним аргументом, дающим предупреждение. Это звучит сложно, не говоря уже о специфике компилятора, поэтому я бы сделал для этого общий шаблон, если он еще не включен в Boost и не будет способствовать его использованию.

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

Для того, чтобы неконтейнер был расширен с помощью функций initializer_list, вполне возможно было бы специально избежать создания конструктора с одним аргументом, который может быть ошибочным. Таким образом, конструктор one-arg будет удален в обновленном классе, или для списка инициализаторов сначала потребуются другие (возможно, тег) аргументы. То есть, не делайте этого, под угрозой pie-in-face при просмотре кода.

Даже для контейнероподобных классов класс, который не является стандартным классом библиотеки, может наложить, что форма конструктора с одним аргументом больше не доступна. Например. C c3 (size); должен быть записан как C c3 (size, C()); или предназначен для принятия аргумента перечисления, что также удобно указывать с инициализацией на одно значение по сравнению с зарезервированным размером, поэтому вы можете аргументировать его как функцию и указать код, который начинается с отдельный звонок в резерв. Поэтому снова не делайте этого, если я могу разумно избежать этого.