Прямая инициализация
Инициализирует объект из явного набора аргументов конструктора.
Синтаксис
T объект ( аргумент );
T объект |
(1) | ||||||||
T объект { аргумент };
|
(2) | (начиная с C++11) | |||||||
T ( другой )
T |
(3) | ||||||||
static_cast< T >( другой )
|
(4) | ||||||||
new T(аргументы, ...)
|
(5) | ||||||||
Класс::Класс() : элемент(аргументы, ...) { ... }
|
(6) | ||||||||
[аргумент](){ ... }
|
(7) | (начиная с C++11) | |||||||
Объяснение
Прямая инициализация выполняется в следующих случаях:
Эффекты прямой инициализации:
- Если
Tявляется типом массива,
|
(до C++20) |
struct A
{
explicit A(int i = 0) {}
};
A a[2](A(1)); // OK: инициализирует a[0] с помощью A(1) и a[1] с помощью A()
A b[2]{A(1)}; // ошибка: неявная инициализация списком копирования b[1]
// из {} выбранного явного конструктора
|
(начиная с C++20) |
- Если
Tявляется типом класса,
|
(начиная с C++17) |
- проверяются конструкторы
T, и с помощью разрешения перегрузки выбирается наилучшее совпадение. Затем для инициализации объекта вызывается конструктор.
- проверяются конструкторы
struct B
{
int a;
int&& r;
};
int f();
int n = 10;
B b1{1, f()}; // OK, продлевается время жизни
B b2(1, f()); // верно, но висячая ссылка
B b3{1.0, 1}; // ошибка: сужающее преобразование
B b4(1.0, 1); // верно, но висячая ссылка
B b5(1.0, std::move(n)); // OK
|
(начиная с C++20) |
- Иначе, если
Tявляется неклассовым типом, но исходный тип является классовым, проверяются функции преобразования исходного типа и его базовых классов, если таковые имеются, и путём разрешения перегрузки выбирается наилучшее совпадение. Затем выбранное определяемое пользователем преобразование используется для преобразования выражения инициализатора в инициализируемый объект. - Иначе, если
Tравноboolа исходный тип это std::nullptr_t, значением инициализированного объекта будетfalse. - Иначе при необходимости используются стандартные преобразования для преобразования значения другой в cv-неквалифицированную версию
T, а начальным значением инициализируемого объекта является (возможно преобразованное) значение.
Примечание
Прямая инициализация более разрешительна, чем инициализация копированием: при инициализации копированием учитываются только конструкторы, не являющиеся явными, и неявные определяемые пользователем функции преобразования, а при прямой инициализации учитываются все конструкторы и все определяемые пользователем функции преобразования.
В случае неоднозначности между объявлением переменной с использованием синтаксиса прямой инициализации (1) (с круглыми скобками) и объявлением функции компилятор всегда выбирает объявление функции. Это правило устранения неоднозначности иногда противоречит здравому смыслу и называется самый неприятный синтаксический анализ.
#include <fstream>
#include <iterator>
#include <string>
int main()
{
std::ifstream file("data.txt");
// Следующее это объявление функции:
std::string foo1(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
// Оно объявляет функцию с именем foo1, тип возвращаемого значения которой std::string,
// первый параметр имеет тип std::istreambuf_iterator<char> и имя "file",
// второй параметр не имеет имени и имеет тип std::istreambuf_iterator<char>(),
// который переписывается в тип указателя на функцию
// std::istreambuf_iterator<char>(*)()
// Исправление до C++11 (для объявления переменной) — добавьте
// дополнительные круглые скобки вокруг одного из аргументов:
std::string str1( (std::istreambuf_iterator<char>(file) ),
std::istreambuf_iterator<char>());
// Исправление после С++11 (для объявления переменной) — используйте
// инициализацию списком для любого из аргументов:
std::string str2(std::istreambuf_iterator<char>{file}, {});
}
Пример
#include <iostream>
#include <memory>
#include <string>
struct Foo
{
int mem;
explicit Foo(int n) : mem(n) {}
};
int main()
{
std::string s1("тест"); // конструктор из const char*
std::string s2(10, 'a');
std::unique_ptr<int> p(new int(1)); // OK: разрешены явные конструкторы
// std::unique_ptr<int> p = new int(1); // ошибка: конструктор явный
Foo f(2); // f инициализируется напрямую:
// параметр конструктора n инициализируется копированием из rvalue 2
// f.mem инициализируется напрямую из параметра n
// Foo f2 = 2; // ошибка: конструктор явный
std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem << '\n';
}
Вывод:
тест aaaaaaaaaa 1 2