Инициализация копированием
Инициализирует объект из другого объекта.
Синтаксис
T объект = другой;
|
(1) | ||||||||
T объект = {другой};
|
(2) | (до C++11) | |||||||
f(другой)
|
(3) | ||||||||
return другой;
|
(4) | ||||||||
throw объект;
|
(5) | ||||||||
T массив[N] = {другая-последовательность};
|
(6) | ||||||||
Объяснение
Инициализация копированием выполняется в следующих случаях:
T объявляется с инициализатором, состоящим из знака равенства, за которым следует выражение.T объявляется с инициализатором, состоящим из знака равенства, за которым следует заключённое в фигурные скобки выражение (Примечание: начиная с C++11, это классифицируется как инициализация списком, а сужающее преобразование не разрешено).Эффекты инициализации копированием:
|
(начиная с C++17) |
- Если
Tявляется типом класса, а cv-неквалифицированная версия типа другой являетсяTили класс, производный отT, проверяются неявные конструкторы классаT, и с помощью разрешения перегрузки выбирается наилучшее совпадение. Затем вызывается конструктор для инициализации объекта.
- Если
Tявляется типом класса, а cv-неквалифицированная версия типа другой не являетсяTи не является производной отT, или еслиTне является классовым типом, но тип другой является типом класса, проверяются определённые пользователем последовательности преобразования, которые могут преобразовывать из типа другой вT(или в тип, производный отT, еслиTявляется типом класса и доступна функция преобразования), и лучшая из них выбирается с помощью разрешения перегрузки. Результат преобразования, который представляет собой временное rvalue (до C++11)временное prvalue (начиная с C++11)
(до C++17)выражение prvalue (начиная с C++17) cv-неквалифицированной версииT, если использовался конвертирующий конструктор, затем используется для прямой инициализации объекта. Последний шаг обычно оптимизируется, и результат преобразования создаётся непосредственно в памяти, выделенной для целевого объекта, но для этого требуется доступность соответствующего конструктора (перемещения или копирования), даже если он не используется. (до C++17)
- Иначе (если ни тип
T, ни тип другой не являются типами класса), при необходимости используются стандартные преобразования для преобразования значения другой в cv-неквалифицированную версиюT.
Примечание
Инициализация копированием менее разрешительна, чем прямая инициализация: явные конструкторы не являются конвертирующими конструкторами и не учитываются при инициализации копированием.
struct Exp { explicit Exp(const char*) {} }; // не конвертирует из const char*
Exp e1("abc"); // OK
Exp e2 = "abc"; // Ошибка, инициализация копированием не учитывает явный конструктор
struct Imp { Imp(const char*) {} }; // конвертирует из const char*
Imp i1("abc"); // OK
Imp i2 = "abc"; // OK
Кроме того, неявное преобразование при инициализации копированием должно производить T непосредственно из инициализатора, в то время как, например, прямая инициализация предполагает неявное преобразование инициализатора в аргумент конструктора T.
struct S { S(std::string) {} }; // неявно конвертируется из std::string
S s("abc"); // OK: преобразование из const char[4] в std::string
S s = "abc"; // Ошибка: нет преобразования из const char[4] в S
S s = "abc"s; // OK: преобразование из std::string в S
Если другой является выражением rvalue, разрешением перегрузки будет выбран конструктор перемещения и вызван во время инициализации копированием. Это по-прежнему считается инициализацией копированием; для этого случая нет специального термина (например, инициализация перемещением).
Неявное преобразование определяется в терминах инициализации копированием: если объект типа T может быть инициализирован копированием выражением E, тогда E неявно преобразуется в T.
Знак равенства = в инициализации копированием именованной переменной не связан с оператором присваивания. Перегрузки оператора присваивания не влияют на инициализацию копированием.
Пример
#include <memory>
#include <string>
#include <utility>
struct A
{
operator int() { return 12;}
};
struct B
{
B(int) {}
};
int main()
{
std::string s = "test"; // OK: конструктор не является явным
std::string s2 = std::move(s); // эта инициализация копированием выполняет перемещение
// std::unique_ptr<int> p = new int(1); // ошибка: конструктор явный
std::unique_ptr<int> p(new int(1)); // OK: прямая инициализация
int n = 3.14; // преобразование значения с плавающую запятой в целое
const int b = n; // константа не имеет значения
int c = b; // ...другой способ
A a;
B b0 = 12;
// B b1 = a; // < ошибка: запрошено преобразование из 'A' в нескалярный тип 'B'
B b2{a}; // < идентично вызову A::operator int(), затем B::B(int)
B b3 = {a}; // <
auto b4 = B{a}; // <
// b0 = a; // < ошибка, требуется перегрузка оператора присваивания
[](...){}(c, b0, b3, b4); // сделать вид, что эти переменные используются
}
Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 5 | C++98 | cv-квалификация целевого типа применяется к временному объекту, инициализированному конструктором преобразования |
временный объект не имеет cv-квалификации |
| CWG 177 | C++98 | категория значения временного объекта, созданного при инициализации копированием объекта класса, не указана |
указана как rvalue |