Как я могу инициализировать массив без копирования или создания временных элементов? Когда элемент имеет явно delete
d копируя или перемещая конструктор, я могу инициализировать массив только в том случае, если у элемента есть ctor по умолчанию или ctor со всеми аргументами по умолчанию, и я делаю одно из следующего: (a) просто объявить массив, (b) прямую инициализацию и нулевую инициализацию массив, или (c) скопировать инициализировать и инициализировать массив нулем. Ни прямая (но не нулевая) инициализация, ни копирование (но не нулевая) инициализация не компилируются.
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
//Foo(Foo&&) = delete; // <-- gives same effect
int num;
};
int main()
{
// Resultant arrays for 'a1', 'a2', and 'a3' are two
// 'Foo' elements each with 'num' values of '5':
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization and zero initialization
Foo a3[2] = {}; // copy initialization and zero initialization
Foo a4[2] {5, 5}; // direct initialization -> ERROR
Foo a5[2] = {5, 5}; // copy initialization -> ERROR
}
a1
, a2
, а также a3
считать инициализации? например a1
является объявлением, но его элементы получают начальные, хотя и по умолчанию, значения.Декларация кода Foo a2[2];
объявляет массив. Единственный способ инициализировать массив — это инициализация списка (т. Е. Заключенный в скобки список из нуля или более элементов), а поведение описывается в разделе стандарта под названием агрегатная инициализация. (Семестр совокупный относится к массивам и классам, которые соответствуют определенным критериям).
В совокупной инициализации наличие =
не имеет значения. Основное его определение в C ++ 14 [dcl.init.aggr] / 2:
Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся в качестве инициализаторов для элементов агрегата в порядке возрастания индекса или элемента. Каждый член инициализируется копией из соответствующего предложения инициализатора.
Также / 7:
Если в списке меньше инициализаторов-предложений, чем элементов в агрегате, то каждый элемент, который не был явно инициализирован, должен быть инициализирован из своего фигурного или равного инициализатора или, если нет фигурного или равного
инициализатор, из пустого списка инициализаторов (8.5.4).
Из этого вы можете видеть, что инициализация копии всегда используется для каждого предоставленного инициализатора. Следовательно, когда инициализатор является выражением, для класса должен существовать доступный конструктор копирования / перемещения.
Однако (как предлагает Anty) вы можете сделать инициализатор другим списком. Инициализация копирования с использованием списка называется copy-list-initialization:
Foo a6[2] = {{6}, {6}};
Когда один Foo
инициализируется списком, это не агрегатная инициализация (так как Foo
это не совокупность). Таким образом, правила отличаются от тех, которые обсуждались выше. Инициализация списка копирования неагрегированного класса выполняется в списке инициализации в [dcl.init.list] /3.4, который указывает для Foo
что инициализаторы в списке сопоставляются с аргументами конструктора с использованием разрешения перегрузки. На этом этапе Foo(int)
будет выбран конструктор, то есть копирующий конструктор не требуется.
Для полноты я упомяну ядерный вариант:
typename std::aligned_storage< sizeof(Foo), alignof(Foo) >::type buf[2];
::new ((void *)::std::addressof(buf[0])) Foo(5);
::new ((void *)::std::addressof(buf[1])) Foo(5);
Foo *a7 = reinterpret_cast<Foo *>(buf);
// ...
a7[0].~Foo();
a7[1].~Foo();
Очевидно, что это последнее средство, когда вы не можете достичь своей цели каким-либо другим способом.
Примечание 1: вышеизложенное относится к C ++ 14. Я полагаю, что в C ++ 17 так называемое «гарантированное исключение копирования» изменит инициализацию копирования, чтобы фактически не требовать конструктора копирования / перемещения. Я надеюсь, что обновлю этот ответ, как только стандарт будет опубликован. Также были некоторые сложности с инициализацией агрегатов в черновиках.
В вашем случае вы все еще можете использовать эти конструкции:
Foo a4[2] = {{4},{3}};
или же
Foo a5[2] {{4},{3}};
Вы также можете создать указатель с помощью malloc, а затем использовать синтаксис массива (если класс является POD). Пример:
class A {
public:
int var1;
int var2;
public int add(int firstNum, int secondNum) {
return firstNum + secondNum;
}
}
A * p = 0;
while(!p) {
p = (A*)malloc(sizeof(A) * 2);
}
p[0] = {2, 3};
p[1] = {2, 5};
Есть также способ инициализировать массив как временное значение, но я забыл, как это сделать.
Вы можете напрямую инициализировать массив объектов, если класс является POD (обычные старые данные). Чтобы класс был POD, у него не должно быть конструкторов, деструкторов или виртуальных методов. Все в классе также должно быть объявлено общедоступным, чтобы оно было POD. По сути, POD-класс — это просто структура в стиле c, в которой могут быть методы.
У меня нет стандарта C ++, и цитирование его, вероятно, будет единственным способом доказать мои слова. Поэтому, чтобы ответить на каждый ваш вопрос, я могу только сказать:
хх
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
int num;
};
int main()
{
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization
Foo a3[2] = {}; // also direct initialization
Foo a4[2] { {5}, {5} }; // also direct initialization
Foo a5[2] = { {5}, {5} }; // also direct initialization
}
Инициализация скобок — это не объявление и копирование, это отдельная языковая конструкция. Это может очень хорошо просто на месте построить элементы. Единственная ситуация, когда я не уверен, применимо ли это { Foo(5), Foo(5) }
инициализация, так как она явно запрашивает создание временных. { 5, 5}
вариант такой же, потому что для инициализации массива необходим инициализированный скобками список Foo
объекты. Так как вы не создаете ничего, он будет использовать конструктор для временных { Foo(5), Foo(5) }
, { { 5 }, { 5 } }
вариант компилируется, потому что компилятор знает, что он может построить Foo
объект из предоставленного { 5 }
инициализатор и, следовательно, не требует временных — хотя я не знаю точную стандартную формулировку, которая позволяет это.
Нет, я не думаю, что все это ошибки.
Я помню строку в стандарте C ++, в которой в основном говорится, что компилятор всегда может заменить инициализацию присваивания прямой инициализацией при создании новой переменной.
хх
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
{ 5 }
будет интерпретироваться как «инициализатор для объекта Foo», тогда как обычный 5
будет восприниматься как «значение, которое может быть преобразовано во временный объект Foo». Списки инициализаторов обычно должны содержать либо списки инициализаторов для элементов, либо элементы точного типа элементов. Если дано что-то другое, будет создан временный.