Объявление и инициализация массива в С++ 11

Вот 8 способов объявить и инициализировать массивы в С++ 11, которые выглядят нормально под g++:

/*0*/ std::array<int, 3> arr0({1, 2, 3});
/*1*/ std::array<int, 3> arr1({{1, 2, 3}});
/*2*/ std::array<int, 3> arr2{1, 2, 3};
/*3*/ std::array<int, 3> arr3{{1, 2, 3}};
/*4*/ std::array<int, 3> arr4 = {1, 2, 3};
/*5*/ std::array<int, 3> arr5 = {{1, 2, 3}};
/*6*/ std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
/*7*/ std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});

Каковы правильные в соответствии со строгим стандартом (и предстоящим стандартом С++ 14)? Каковы наиболее распространенные/используемые и те, которых следует избегать (и по какой причине)?

Ответ 1

С++ 11 summary/TL; DR

  • Из-за дефекта дефекта скобки, примеры 0, 2, 6 не должны работать. Однако последняя версия компиляторов реализует предлагаемую резолюцию для этого дефекта, чтобы эти примеры работали.
  • Как не указано, содержит ли std::array необработанный массив. Поэтому примеры 1, 3, 5, 7 не должны работать. Однако я не знаю о реализации стандартной библиотеки, где они не работают (на практике).
  • Пример 4 всегда будет работать: std::array<int, 3> arr4 = {1, 2, 3};

Я бы предпочел версию 4 или версию 2 (с исправлением исправления), поскольку они инициализируются напрямую и требуются/могут работать.

Для стиля Sutter AAA вы можете использовать auto arrAAA = std::array<int, 3>{1, 2, 3};, но для этого требуется исправление исправления фигур.


std::array требуется быть агрегатом [array.overview]/2, это означает, что у него нет конструкторов, предоставляемых пользователем (т.е. только по умолчанию, копировать, перемещать ctor).


std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});

Инициализация с помощью (..) является прямой инициализацией. Для этого требуется вызов конструктора. В случае arr0 и arr1 только конструктор copy/move жизнеспособны. Следовательно, эти два примера означают создание временного std::array из списка braced-init и копирование/перенос его в пункт назначения. С помощью copy/move elision компилятор может исключить эту операцию копирования/перемещения, даже если он имеет побочные эффекты.

<суб > N.B. хотя временные значения являются prvalues, он может вызывать копию (семантически, до копирования), поскольку перемещение ctor std::array может быть неявно объявлено, например. если он был удален.


std::array<int, 3> arr6 = std::array<int, 3>({1, 2, 3});
std::array<int, 3> arr7 = std::array<int, 3>({{1, 2, 3}});

Это примеры инициализации копирования. Созданы два временных файла:

  • через скопированный-init-list {1, 2, 3}, чтобы вызвать конструктор copy/move
  • через выражение std::array<int, 3>(..)

последний временный затем копируется/перемещается в указанную целевую переменную. Создание обоих временных времен может быть отменено.

Насколько мне известно, реализация может написать конструктор explicit array(array const&) = default; и не нарушать стандарт; это приведет к тому, что эти примеры плохо сформированы. (Эта возможность исключена [container.requirements.general], прислугой Дэвида Краусса, см. это обсуждение.)


std::array<int, 3> arr2{1, 2, 3};
std::array<int, 3> arr3{{1, 2, 3}};
std::array<int, 3> arr4 = {1, 2, 3};
std::array<int, 3> arr5 = {{1, 2, 3}};

Это агрегатная инициализация. Все они "прямо" инициализируют std::array, не вызывая конструктор std::array и без (семантически) создавая временный массив. Элементы std::array инициализируются с помощью инициализации копирования (см. Ниже).


По теме brace-elision:

В стандарте С++ 11, выравнивание фигурной скобки применяется только к объявлениям формы T x = { a };, но не к T x { a };. Это считается дефектом и будет исправлено в С++ 1y, однако предлагаемая резолюция не является частью стандарта (статус DRWP, см. вверху связанной страницы), и поэтому вы не можете рассчитывать на свой компилятор, реализующий его также для T x { a };.

Следовательно, std::array<int, 3> arr2{1, 2, 3}; (примеры 0, 2, 6), строго говоря, плохо сформированы. Насколько мне известно, недавние версии clang++ и g++ уже позволяют использовать элемент T x { a };.

В примере 6, std::array<int, 3>({1, 2, 3}) использует инициализацию копирования: инициализация для передачи аргумента также является copy-init. Однако дефектное ограничение выбора фигурной скобки: "В объявлении формы T x = { a };" также запрещает выравнивание фигуры для передачи аргумента, поскольку оно не является объявлением и, конечно, не имеет такой формы.


По теме инициализации агрегата:

Как Йоханнес Шауб указывает в комментарии, гарантируется, что вы можете инициализировать std::array со следующим синтаксисом [array.overview]/2:

array<T, N> a = { initializer-list };

Вы можете вывести из этого, , если brace-elision разрешено в форме T x { a };, что синтаксис

array<T, N> a { initializer-list };

хорошо сформирован и имеет то же значение. Однако не гарантируется, что std::array на самом деле содержит необработанный массив в качестве единственного члена данных (см. Также LWG 2310). Я думаю, что одним примером может быть частичная специализация std::array<T, 2>, где есть два элемента данных T m0 и T m1. Поэтому нельзя сделать вывод, что

array<T, N> a {{ initializer-list }};

хорошо сформирован. Это, к сожалению, приводит к тому, что нет гарантированного способа инициализации временного отсутствия std::array для T x { a };, а также означает, что нечетные примеры (1, 3, 5, 7) не требуются для работы.


Все эти способы инициализации std::array в конечном итоге приводят к агрегационной инициализации. Он определяется как копирование инициализации совокупных членов. Тем не менее, инициализация копирования с использованием скопированного списка-init может по-прежнему напрямую инициализировать член агрегата. Например:

struct foo { foo(int); foo(foo const&)=delete; };
std::array<foo, 2> arr0 = {1, 2};      // error: deleted copy-ctor
std::array<foo, 2> arr1 = {{1}, {2}};  // error/ill-formed, cannot initialize a
                                       // possible member array from {1}
                                       // (and too many initializers)
std::array<foo, 2> arr2 = {{{1}, {2}}}; // not guaranteed to work

Первая попытка инициализировать элементы массива из предложений-инициализаторов 1 и 2, соответственно. Эта инициализация копии эквивалентна foo arr0_0 = 1;, которая в свою очередь эквивалентна foo arr0_0 = foo(1);, которая является незаконной (удаленная копия-ctor).

Второй не содержит список выражений, но список инициализаторов, поэтому он не удовлетворяет требованиям [array.overview]/2. На практике std::array содержит элемент данных необработанного массива, который будет инициализирован (только) из первого предложения-инициализатора {1}, второе предложение {2} затем является незаконным.

Третий имеет противоположную проблему как вторую: он работает, если есть элемент данных массива, но это не гарантируется.

Ответ 2

Я считаю, что все они строго соответствуют друг другу, кроме, возможно, arr2. Я бы пошел с arr3 способом, потому что он краток, ясен и определенно действителен. Если arr2 действительно (я просто не уверен), это было бы даже лучше, на самом деле.

Объединение парсеров и фигурных скобок (0 и 1) никогда не сидит со мной хорошо, равные (4 и 5) в порядке, но я предпочитаю более короткую версию, а 6 и 7 просто абсурдно многословны.

Тем не менее, вы можете пойти по-другому, следуя Herb Sutter "почти всегда авто" стиль:

auto arr8 = std::array<int, 3>{{1, 2, 3}};

Ответ 3

Этот ответ связывает отчет , в котором -Wmissing-braces больше не включается по умолчанию при использовании -Wall. Если вы включите -Wmissing-braces, gcc будет жаловаться на 0, 2, 4 и 6 (то же, что и clang).

Разрешения для скобок разрешены для операторов в форме T a = { ... }, но не T a { }.

Почему поведение сценария С++ initializer_list для std::vector и std:: array отличается?

Вот Джеймс Макнеллис ответ:

Однако эти дополнительные фигурные скобки могут быть отменены только "в декларации форма T x = {a};" (С++ 11 §8.5.1/11), то есть, когда старый стиль = используется. Это правило, разрешающее выравнивание фигур, не применяется для прямой инициализации списка. В сноске здесь говорится: "Скобки не могут быть удалены в других целях использования инициализации списка."

Существует отчет о дефекте относительно этого ограничения: CWG defect# 1270. Если принятая предлагаемая резолюция будет принята, то для других форм инициализации списка будет разрешено выравнивание фигур,...

Если предлагаемая резолюция будет принята, будет разрешено устранение конфликтов для других форм инициализации списка, и будет следующее: хорошо сформированные: std:: array y {1, 2, 3, 4};

И Xeo ответьте:

... в то время как std:: array не имеет конструкторов и {1, 2, 3, 4} скобок init-list на самом деле не интерпретируется как std:: initializer_list, но агрегированная инициализация для внутреннего массива C-стиля std:: array (что, где второй набор фигурных скобок происходит от: Один для std:: array, один для внутреннего массива элементов C-стиля).

std::array не имеет конструктора, который принимает initializer_list. По этой причине он рассматривается как агрегатная инициализация. Если бы это было так, это выглядело бы примерно так:

#include <array>
#include <initializer_list>

struct test {
    int inner[3];

    test(std::initializer_list<int> list) {
        std::copy(list.begin(), list.end(), inner);
    }
};

#include <iostream>

int main() { 
    test t{1, 2, 3};
    test t2({1, 2, 3});
    test t3 = {1, 2, 3};
    test t4 = test({1, 2, 3});
    for (int i = 0; i < 3; i++)
        std::cout << t.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t2.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t3.inner[i];
    for (int i = 0; i < 3; i++)
        std::cout << t4.inner[i];
}

Ответ 4

Последние 2 являются избыточными: вы можете использовать 6 форм объявления массива в правой части задания. Кроме того, если ваш компилятор не оптимизирует копию, эти версии менее эффективны.

Двойные скобки требуются с конструктором списка инициализаторов, поэтому ваша третья строка недействительна.