C ++ 17 Оновлення
У C ++ 17 значення A_factory_func()
змінено від створення тимчасового об'єкта (C ++ <= 14) до просто вказання ініціалізації будь-якого об'єкта, на якому цей вираз ініціалізується (слабко кажучи) в C ++ 17. Ці об'єкти (звані "об'єктами результатів") - це змінні, створені декларацією (наприклад a1
), штучними об'єктами, створеними, коли ініціалізація закінчується відкиданням, або якщо об'єкт необхідний для прив'язки посилань (наприклад, в A_factory_func();
. В останньому випадку, об'єкт створюється штучно, називається "тимчасова матеріалізація", оскільки A_factory_func()
не має змінної чи посилання, яка б інакше вимагала б існування об'єкта).
Як приклади в нашому випадку, у випадку a1
і a2
спеціальних правил говориться, що в таких деклараціях об'єкт результату ініціалізатора первинного значення того ж типу, що a1
і змінний a1
, і тому A_factory_func()
безпосередньо ініціалізує об'єкт a1
. Будь-який посередницький A_factory_func(another-prvalue)
склад функціонального стилю не мав би ніякого ефекту, тому що просто "проходить" об'єктом результату зовнішнього первинного значення, щоб бути також об'єктом результату внутрішнього первинного слова.
A a1 = A_factory_func();
A a2(A_factory_func());
Залежить від того, який тип A_factory_func()
повертається. Я припускаю, що він повертає A
- тоді він робить те саме - за винятком випадків, коли конструктор копій явний, тоді перший не вийде. Прочитайте 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Це робиться так само, оскільки це вбудований тип (це означає, що тут не тип класу). Прочитайте 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
Це не те саме. Перший за замовчуванням ініціалізується, якщо A
це не-POD, і не робить ніякої ініціалізації для POD (Прочитайте 8.6 / 9 ). Друга копія ініціалізується: Value - ініціалізує тимчасовий, а потім копіює це значення у c2
(Прочитайте 5.2.3 / 2 та 8.6 / 14 ). Для цього, звичайно, знадобиться не явний конструктор копій (Прочитайте 8.6 / 14 і 12.3.1 / 3 та 13.3.1.3/1 ). Третя створює декларацію функції для функції, c3
яка повертає A
а, яка приймає вказівник функції на функцію, що повертає a A
(Читання 8.2 ).
Поглиблення в ініціалізацію Пряма та ініціалізація копіювання
Хоча вони виглядають однаково і повинні робити те саме, ці дві форми в певних випадках надзвичайно відрізняються. Дві форми ініціалізації - це пряма та ініціалізація копії:
T t(x);
T t = x;
Є поведінка, яку ми можемо віднести до кожного з них:
- Пряма ініціалізація веде себе як виклик функції перевантаженій функції: Функції в цьому випадку є конструкторами
T
(включаючи explicit
їх), і аргументом є x
. Дозвіл перевантаження знайде найкращий конструктор, що відповідає, і при необхідності здійснить будь-яке неявне перетворення.
- Копія ініціалізації конструює неявну послідовність перетворення: Вона намагається перетворити
x
на об'єкт типу T
. (Тоді він може скопіювати цей об’єкт у об'єкт, який ініціалізується, тому конструктор копій теж потрібен - але це не важливо нижче)
Як бачите, ініціалізація копіювання певним чином є частиною прямої ініціалізації щодо можливих неявних перетворень: Хоча пряма ініціалізація має всі конструктори, доступні для виклику, і, крім того, може робити будь-яке неявне перетворення, яке потрібно для узгодження типів аргументів, копіювання ініціалізації може просто встановити одну неявну послідовність перетворення.
Я дуже постарався і отримав наступний код для виведення різного тексту для кожної з цих форм , не використовуючи «очевидний» через explicit
конструктори.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
Як це працює і чому він дає такий результат?
Пряма ініціалізація
Спочатку він нічого не знає про конверсію. Він просто спробує викликати конструктора. У цьому випадку доступний наступний конструктор і відповідає точній відповідності :
B(A const&)
Для виклику цього конструктора не потрібна конверсія, а тим більше визначена користувачем конверсія (зауважте, що тут також не відбувається перетворення кваліфікації const). І так пряма ініціалізація назве це.
Скопіюйте ініціалізацію
Як було сказано вище, ініціалізація копіювання побудує послідовність перетворення, коли a
вона не введена B
або похідна від неї (що, очевидно, в цьому випадку). Тож він шукатиме шляхи здійснення конверсії та знайде наступних кандидатів
B(A const&)
operator B(A&);
Зверніть увагу, як я переписав функцію перетворення: Тип параметра відображає тип this
вказівника, який у функції non-const-члена є не-const. Тепер ми називаємо цих кандидатів x
аргументом. Переможець - це функція перетворення: Оскільки, якщо у нас є дві функції-кандидати, обидві приймають посилання на один і той же тип, то виграє менше версія const (це, до речі, також механізм, який віддає перевагу функції, що не має статусу члена, вимагає не -об'єкти).
Зауважте, що якщо ми змінимо функцію перетворення на функцію члена const, то перетворення неоднозначне (тому що обидва мають тип параметра A const&
тоді): компілятор Comeau відхиляє її належним чином, але GCC приймає її в непедантичному режимі. -pedantic
Хоча перехід на нього також видає належне попередження про неоднозначність.
Я сподіваюся, що це дещо допомагає зрозуміти, чим відрізняються ці дві форми!
A c1; A c2 = c1; A c3(c1);
.