Вот 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)?
Каковы наиболее распространенные / используемые и которых следует избегать (и по какой причине)?
C ++ 11 резюме / TL; DR
std::array
содержит необработанный массив. Поэтому примеры 1, 3, 5, 7 не обязательны для работы. Однако я не знаю реализации стандартной библиотеки, где они не работают (на практике).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>(..)
последний временный затем копируется / перемещается в именованную целевую переменную. Создание обоих временников может быть исключено.
Насколько я знаю, реализация может написать (Эта возможность исключена [container.requirements.general], слава Дэвиду Крауссу, см. это обсуждение.)explicit array(array const&) = default;
конструктор и не нарушать Стандарт; это сделало бы эти примеры плохо сформированными.
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}
тогда незаконно.
Третий имеет противоположную проблему, как второй: он работает, если есть является член массива данных, но это не гарантируется.
Я считаю, что все они строго соответствуют, за исключением, возможно, arr2
, Я бы пошел с arr3
Кстати, потому что он лаконичен, понятен и определенно актуален. Если arr2
действительно (я просто не уверен), это было бы еще лучше, на самом деле.
Объединение скобок и скобок (0 и 1) никогда не подходит мне, равно (4 и 5) — это нормально, но я предпочитаю более короткую версию, а 6 и 7 — просто нелепо многословно.
Тем не менее, вы можете пойти еще одним путем, следуя Стиль Херба Саттера «почти всегда авто»:
auto arr8 = std::array<int, 3>{{1, 2, 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];
}
Последние 2 избыточны: вы можете использовать 6 форм объявления массива в правой части присваивания. Кроме того, если ваш компилятор не оптимизирует копию, эти версии менее эффективны.
Двойные фигурные скобки необходимы для конструктора списка инициализаторов, поэтому ваша третья строка недействительна.