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

Вот 8 способов объявить и инициализировать массивы в C ++ 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}});

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

11

Решение

C ++ 11 резюме / TL; DR

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

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

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


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


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

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

Нотабене даже если временные значения являются предварительными значениями, он может вызывать копию (семантически, до удаления копии) в качестве 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}});

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

  • через список фигурных скобок {1, 2, 3} вызвать конструктор копирования / перемещения
  • через выражение 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 инициализируются путем копирования-инициализации (см. ниже).


По теме «Брейс-элис»:

В стандарте C ++ 11 фигурная скобка применяется только к объявлениям формы T x = { a }; но не для T x { a };, Это считается дефектом и будет исправлено в C ++ 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-initialization: инициализация для передачи аргумента также является copy-init. Дефектное ограничение скобки, однако, «В декларации формы T x = { a };«, также запрещает использование скобок для передачи аргументов, поскольку это не декларация и, конечно, не такой формы.


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

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

массив<Т, Н> а = { инициализатора-лист };

Вы можете сделать вывод из этого, если Брейс-исключение допускается в форме T x { a };, что синтаксис

массив<Т, Н> { инициализатора-лист };

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

массив<Т, Н> {{ инициализатора-лист }};

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


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

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

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

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

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

19

Другие решения

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

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

Тем не менее, вы можете пойти еще одним путем, следуя Стиль Херба Саттера «почти всегда авто»:

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

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

Brace Elision допускается для заявлений в форме T a = { ... } но нет T a { },

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

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

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

По этому ограничению есть отчет о дефектах: CWG дефект
# 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];
}
1

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

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

0
По вопросам рекламы [email protected]