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

С++ 0x равномерная инициализация "странность"

Как и многие, я очень рад C++0x. Я пытаюсь изучить и использовать новые функции в новых проектах, чтобы я мог написать лучший, самый простой в обслуживании код.

Излишне говорить, что мне нравится идея новых инициализаторов. Поэтому я смотрю на них, и это имеет смысл для меня:

T x = { 1, 2, 3 }; // like a struct or native array
T x({1, 2, 3});    // copy construct something like an "object literal" in other languages... cool!
return {1, 2, 3};  // similar to above, but returning it, even cooler!

Что для меня не имеет смысла:

T x{1, 2, 3};

Он просто чувствует... странно. Я не уверен, какой синтаксис, который люди хотят использовать, это подражание, это просто не кажется "правильным".

Что за дизайн/мысль за этим синтаксисом?

Единственный пример, где кажется, что он имеет значение, выглядит примерно так:

std::vector<int> the_vec{4};

который будет вызывать конструктор списка инициализаторов, но почему бы просто не написать это:

std::vector<int> the_vec = {4};

И делаете то, с кем все уже комфортно?

4b9b3361

Ответ 1

Что за дизайн/мысль за этим синтаксисом?

С одной стороны, синтаксис привязки позволяет избежать досадных разбора:

T x(); // function declaration
T x{}; // value-initialized object of type 'T' named 'x'

В С++ 03 наиболее близким к этому может быть T x((T())); или T x = T();, для чего требуется T иметь доступный конструктор копирования.

Ответ 2

Во-первых, у вас действительно есть два варианта:

T x = { 1, 2, 3 };
T x{1, 2, 3};

Эти два действительно выполняют ту же инициализацию, за исключением того, что первый недопустим, если он выбирает конструктор explicit. В противном случае они идентичны. Первая называется "копирование списка-инициализация", а вторая - "direct list-initialization".

Понятие состоит в том, что форма с = назначает "составное значение" - значение, состоящее из 3 целых чисел. И он инициализирует x этим значением. Для такой инициализации допускаются только конструкторы non explicit. Концепция для x{1, 2, 3} (без знака равенства) заключается в том, что вы инициализируете переменную с 3 значениями - концептуально не составное значение, а 3 отдельных значения, которые вы получаете сразу. Вы можете сказать, что это "вызов конструктора" в самом общем смысле этого термина.

Другая инициализация, которую вы показали, действительно отличается от двух предыдущих:

T x({1, 2, 3});

Он вызывает только конструкторы T с {1, 2, 3} в качестве аргумента. Он не выполняет никаких причудливых действий, таких как инициализация массива, если T является массивом или инициализирует члены структуры, если T является агрегатной структурой/классом. Если T не является классом, это объявление недопустимо. Но если T имеет конструктор копирования или перемещения, он может, в свою очередь, использовать этот конструктор для создания временной T путем копирования-инициализации списка копий и привязки ссылочного параметра конструктора копирования/перемещения к этому временному. Я считаю, что вам не понадобится эта форма часто в реальном коде.


Все это записано в документах предложений комитета для списков инициализаторов. В этом случае вы хотите взглянуть на Списки инициализаторов - альтернативный механизм и обоснование в разделе "Представление программистов типов инициализации":

Мы заметили, что опытные программисты, которые знают разницу между копировальными, инициализация и прямая инициализация часто ошибочно считают, что первая менее эффективна, чем последняя. (На практике, когда обе инициализации имеют смысл, они одинаково эффективны.)

Мы находим, напротив, что более полезно думать об этих вещах в разных терминах:

  • построение путем вызова конструктора ( "ctor-call" )
  • построение путем переноса значения ( "преобразование" )

(Как это бывает, первая соответствует "прямой инициализации", а вторая - "копировать- инициализация", но стандартные термины не помогают программисту.)

Позже они находят

Заметим, что поскольку мы рассматриваем { ... } в

X x = { ... };

как одно значение, оно не эквивалентно

X x{ ... };

где { ... } - это список аргументов для вызова конструктора (мы подчеркиваем его, потому что он отличается от N2531).

Правила, изложенные в С++ 0x FDIS, немного отличаются от представленных в этой статье, но обоснование, представленное в этой статье, сохраняется и реализуется в С++ 0x FDIS.

Ответ 3

Данные ответы велики с теоретической точки зрения, но, возможно, было бы полезно и несколько практических примеров. С равномерной инициализацией теперь можно писать конструкции, которые ранее были просто невозможны. Например:

  • Инициализировать массивы элементов.

  • Глобальные контейнеры констант (например, карты).

В частности:

class Foo
{
  int data[3];
public:
  Foo() : data{1,2,3} { }
};

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

const std::map<int, std::string> labels {
  { 1 , "Open" },
  { 2 , "Close" },
  { 3 , "Reboot" } };

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

Ответ 4

Я думаю, что синтаксическая однородность очень важна в общем программировании. Например, рассмотрим,

#include <utility>
#include <tuple>
#include <vector>

template <class T>
struct Uniform {
  T t;
  Uniform() : t{10, 12} {}
};

int main(void)
{
  Uniform<std::pair<int, int>> p;
  Uniform<std::tuple<int, int>> t;
  Uniform<int [2]> a;
  Uniform<std::vector<int>> v; // Uses initializer_list

  return 0;
}