Объявление и инициализация массива в 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>(..)

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

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


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 форм объявления массива в правой части назначения. Кроме того, если ваш компилятор не оптимизирует копию, эти версии менее эффективны.

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