Объявление и инициализация массива в 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) ? Каковы наиболее распространенные / используемые и те, которых следует избегать (и по какой причине) ?
4 ответов
C++11 summary / TL; DR
- из-за дефекта elision скобки примеры 0, 2, 6 не требуются для работы. Однако последняя версия компиляторов реализует предлагаемое решение для этого дефекта, так что эти примеры будут работать.
- как не указано ли
std::array
содержит необработанный массив. Таким образом, примеры 1, 3, 5, 7 не требуются для работы. Однако я не знаю стандартной реализации библиотеки, где они не работают (в практика.) - Пример 4 всегда будет работать:
std::array<int, 3> arr4 = {1, 2, 3};
я бы предпочел версию 4 или версию 2 (с исправлением скобки elision), так как они инициализируются напрямую и требуются/могут работать.
для стиля AAA Саттера вы можете использовать auto arrAAA = std::array<int, 3>{1, 2, 3};
, но для этого требуется исправление скобки elision.
std::array
требуется, чтобы быть aggregate [массив.обзор] / 2, это означает, что у него нет пользовательских конструкторов (т. е. только по умолчанию, копировать, перемещать ctor).
std::array<int, 3> arr0({1, 2, 3});
std::array<int, 3> arr1({{1, 2, 3}});
инициализации с (..)
это прямой инициализации. Для этого требуется вызов конструктора. В случае arr0
и arr1
, жизнеспособен только конструктор копирования / перемещения. Следовательно, эти два примера означают создать временную std::array
из списка braced-init и скопируйте / переместите его в пункт назначения. Через copy/move elision компилятор разрешено elide эта операция копирования / перемещения, даже если она имеет сторону эффекты.
N. B. даже если временные значения являются prvalues, он может вызвать копию (семантически, до копирования 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}});
это примеры инициализации копирования. Есть два временных созданных:
- через braced-init-list
{1, 2, 3}
для вызова конструктора копирования/перемещения - через выражение
std::array<int, 3>(..)
последний временный затем копируется / перемещается в именованную целевую переменную. Создание обоих временных центров может быть отменено.
насколько я знаю, реализация может написать (эта возможность исключается [container.требования.генерал], слава Дэвиду Крауссу, см. этот обсуждение.)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
инициализируются с помощью copy-initialization (см. ниже).
на тему скобки-elision:
в стандарте C++11 скобка elision применяется только к объявлениям формы 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 };
уже.
In пример 6, std::array<int, 3>({1, 2, 3})
использует copy-initialization: инициализация для передачи аргумента также является copy-init. Однако дефектное ограничение brace elision, " в декларации формы T x = { a };
", также запрещает использование скобок для передачи аргументов, поскольку это не декларация и, конечно, не такой формы.
на тему агрегат-инициализации:
As Йоханнес Шоб указывает в a комментарий, это только гарантирует, что вы можете инициализировать std::array
со следующим синтаксисом [array.обзор]/2:
array<T, N> a = { initializer-list };
из этого можно сделать вывод,если скобка-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
временное без расчалки elision для T x { a };
, а также означает, что нечетные примеры (1, 3, 5, 7) не обязаны работать.
все эти способы инициализации std::array
в конце концов привести к агрегатной инициализации. Он определяется как инициализация копирования элементов aggregate. Тем не менее, инициализация копирования с помощью braced-init-list все еще может напрямую инициализировать элемент aggregate. Например:
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);
что является незаконным (удален copy-ctor).
второй не содержит списка выражений, но список инициализаторов, поэтому он не отвечает требованиям [array.обзор]/2. На практике, std::array
содержит элемент данных необработанного массива, который будет инициализирован (только) из первого предложения инициализатора {1}
второй пункт {2}
тогда это незаконно.
третий имеет противоположную проблему, как второй: он работает, если есть is элемент данных массива, но это это не гарантировано.
Я считаю, что они все строго соответствуют, за исключением, возможно,arr2
. Я бы пошел с arr3
путь, потому что он сжат, ясен и определенно действителен. Если arr2
действительно (я просто не уверен), это было бы еще лучше, на самом деле.
сочетание паренов и скобок (0 и 1) никогда не сидит хорошо со мной, равные (4 и 5) в порядке, но я просто предпочитаю более короткую версию, а 6 и 7 просто абсурдно многословны.
тем не менее, вы можете пойти с еще одним путь, следующий Херб Саттер "почти всегда "авто"," стиль:
auto arr8 = std::array<int, 3>{{1, 2, 3}};
этой ответ ссылки a сообщить об ошибке, в котором -Wmissing-braces
больше не включается по умолчанию при использовании -Wall
. Если вы включите -Wmissing-braces
, gcc
будет жаловаться на 0, 2, 4 и 6 (так же, как clang
).
скобка elision допускается для операторов в форме T a = { ... }
а не T a { }
.
почему поведение c++ initializer_list для std::vector и std:: array отличается?
здесь Джеймс McNellis'ы ответ:
однако эти дополнительные фигурные скобки могут быть удалены только " в декларации форма T x = { a }; "(C++11 §8.5.1 / 11), то есть когда старый стиль = используется . Это правило, разрешающее выделение скобок, не применяется для прямой инициализации списка. Сноска здесь гласит: "скобки не могут быть удалены в других случаях используется инициализация списка."
существует отчет о дефекте, касающийся этого ограничения: CWG дефект #1270. Если предлагаемая резолюция будет принята, то будут разрешены другие формы инициализации списка ...
если предложенная резолюция будет принята, то будет позволено elision расчалки для других форм инициализации списка, и следующее будет правильно построенный: с std::массив 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 форм объявления массива в правой части назначения. Кроме того, если ваш компилятор не оптимизирует копию, эти версии менее эффективны.
двойные фигурные скобки требуются с конструктором списка инициализаторов, поэтому ваша третья строка недопустима.