Обновление C ++ 17
В C ++ 17 значение A_factory_func()
изменилось с создания временного объекта (C ++ <= 14) на простое указание инициализации любого объекта, которому это выражение инициализируется (условно говоря) в C ++ 17. Эти объекты (называемые «объектами результата») являются переменными, созданными объявлением (например a1
), искусственными объектами, созданными, когда инициализация заканчивается тем, что отбрасывается, или если объект необходим для привязки ссылки (например, в A_factory_func();
. В последнем случае объект создается искусственно, называется «временная материализация», потому A_factory_func()
что не имеет переменной или ссылки, которая в противном случае потребовала бы существования объекта).
В качестве примеров в нашем случае, в случае a1
и a2
специальных правил говорится, что в таких объявлениях результирующий объект инициализатора prvalue того же типа, что a1
и переменный a1
, и, следовательно, A_factory_func()
непосредственно инициализирует объект a1
. Любое промежуточное приведение функционального стиля не будет иметь никакого эффекта, потому что A_factory_func(another-prvalue)
просто «проходит» через объект результата внешнего значения prvalue, чтобы быть также объектом результата внутреннего значения 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());
Это не то же самое. Первый default-initialized, если 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
(Читать 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
указателя, который в неконстантной функции-члене является неконстантным. Теперь мы называем этих кандидатов x
аргументом. Победителем является функция преобразования: поскольку если у нас есть две функции-кандидата, которые принимают ссылку на один и тот же тип, выигрывает менее константная версия (кстати, это также механизм, который предпочитает вызовы неконстантных функций-членов для объекты).
Обратите внимание, что если мы изменим функцию преобразования на постоянную функцию-член, то преобразование будет неоднозначным (потому что оба имеют тип параметра A const&
затем): компилятор Comeau отклоняет его должным образом, но GCC принимает его в непедантичном режиме. Однако переключение на -pedantic
заставляет выводить правильное предупреждение о неоднозначности.
Надеюсь, это поможет понять, как эти две формы отличаются!
A c1; A c2 = c1; A c3(c1);
.