Aktualizacja C ++ 17
W C ++ 17 znaczenie A_factory_func()
zmieniło się z tworzenia obiektu tymczasowego (C ++ <= 14) na samo określenie inicjalizacji dowolnego obiektu, na który to wyrażenie jest inicjowane (luźno mówiąc) w C ++ 17. Te obiekty (zwane „obiektami wynikowymi”) są zmiennymi utworzonymi przez deklarację (jak a1
), sztuczne obiekty utworzone, gdy inicjalizacja kończy się odrzuceniem, lub jeśli obiekt jest potrzebny do powiązania odwołania (jak, w A_factory_func();
. W ostatnim przypadku obiekt jest sztucznie tworzony, zwany „materializacją tymczasową”, ponieważA_factory_func()
nie ma zmiennej ani odwołania, które w innym przypadku wymagałoby istnienia obiektu).
Jako przykłady w naszym przypadku, w przypadku a1
i a2
zasad specjalnych, mówi się, że w takich deklaracjach obiekt wynikowy inicjalizatora wartości tego samego typu co a1
zmienny a1
, a zatem A_factory_func()
bezpośrednio inicjuje obiekt a1
. Żadna pośrednia obsada w stylu funkcjonalnym nie miałaby żadnego efektu, ponieważ A_factory_func(another-prvalue)
po prostu „przechodzi” przez obiekt wynikowy wartości zewnętrznej, która jest również obiektem wynikowym wartości wewnętrznej.
A a1 = A_factory_func();
A a2(A_factory_func());
Zależy, jaki typ A_factory_func()
zwraca. Zakładam, że zwraca A
- i robi to samo - z wyjątkiem tego, że gdy konstruktor kopii jest jawny, to pierwszy nie powiedzie się. Przeczytaj 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Robi to samo, ponieważ jest to typ wbudowany (w tym przypadku nie jest to typ klasy). Przeczytaj 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
To nie robi tego samego. Pierwszy domyślnie inicjuje, jeśli nie A
jest POD, i nie wykonuje żadnej inicjalizacji dla POD (odczyt 8.6 / 9 ). Druga kopia inicjuje: Wartość inicjuje wartość tymczasową, a następnie kopiuje tę wartość do c2
(Przeczytaj 5.2.3 / 2 i 8.6 / 14 ). Będzie to oczywiście wymagało jawnego konstruktora kopii ( odczyty 8.6 / 14 i 12.3.1 / 3 i 13.3.1.3/1 ). Trzeci tworzy deklarację funkcji dla funkcji, c3
która zwraca an A
i która przenosi wskaźnik funkcji do funkcji zwracającej a A
(Odczyt 8.2 ).
Wchodzenie w inicjalizacje Bezpośrednia i kopiowanie inicjalizacja
Mimo że wyglądają identycznie i powinny robić to samo, te dwie formy są bardzo różne w niektórych przypadkach. Dwie formy inicjalizacji to inicjalizacja bezpośrednia i kopiowanie:
T t(x);
T t = x;
Istnieje zachowanie, które możemy przypisać każdemu z nich:
- Bezpośrednia inicjalizacja zachowuje się jak wywołanie funkcji dla przeciążonej funkcji: Funkcje, w tym przypadku, są konstruktorami
T
(włączając je explicit
), a argumentem jest x
. Rozdzielczość przeciążenia znajdzie najlepszego pasującego konstruktora, a gdy zajdzie taka potrzeba, dokona jakiejkolwiek niejawnej konwersji wymaganej.
- Inicjalizacja kopiowania tworzy niejawną sekwencję konwersji: Próbuje przekonwertować
x
na obiekt typu T
. (Następnie może skopiować ten obiekt do obiektu inicjowanego, więc potrzebny jest również konstruktor kopiowania - ale nie jest to ważne poniżej)
Jak widać, inicjalizacja kopiowania jest w pewnym sensie częścią bezpośredniej inicjalizacji w odniesieniu do możliwych niejawnych konwersji: Chociaż inicjalizacja bezpośrednia ma wszystkie konstruktory do wywołania, a ponadto może wykonać dowolną niejawną konwersję, która musi pasować do typów argumentów, inicjalizacja kopii może po prostu skonfigurować jedną domyślną sekwencję konwersji.
Starałem się i otrzymałem następujący kod, aby wypisać inny tekst dla każdej z tych form , bez użycia „oczywistego” za pomocą explicit
konstruktorów.
#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>
Jak to działa i dlaczego generuje taki wynik?
Bezpośrednia inicjalizacja
Najpierw nic nie wie o konwersji. Po prostu spróbuje wywołać konstruktora. W takim przypadku dostępny jest następujący konstruktor, który jest dokładnie zgodny :
B(A const&)
Do wywołania tego konstruktora nie jest wymagana konwersja, a tym bardziej konwersja zdefiniowana przez użytkownika (zauważ, że tutaj również nie występuje konwersja kwalifikacji stałej). I tak to nazwie bezpośrednia inicjalizacja.
Kopiuj inicjalizację
Jak powiedziano powyżej, inicjalizacja kopii skonstruuje sekwencję konwersji, jeśli a
nie zostanie ona wpisana B
ani z niej wyprowadzona (co wyraźnie ma miejsce tutaj). Będzie więc szukał sposobów na konwersję i znajdzie następujących kandydatów
B(A const&)
operator B(A&);
Zwróć uwagę, jak przepisałem funkcję konwersji: Typ parametru odzwierciedla typ this
wskaźnika, który w funkcji składowej innej niż const to non-const. Teraz nazywamy tych kandydatów x
argumentem. Zwycięzcą jest funkcja konwersji: Ponieważ jeśli mamy dwie funkcje kandydujące, obie akceptują odniesienie do tego samego typu, to mniej const wygrywa wersja (nawiasem mówiąc, jest to również mechanizm, który preferuje funkcje niezaangażowane -konst. obiektów).
Zauważ, że jeśli zmienimy funkcję konwersji na stałą element członkowski, wówczas konwersja będzie dwuznaczna (ponieważ oba mają typ parametru A const&
wtedy): Kompilator Comeau odpowiednio ją odrzuca, ale GCC akceptuje ją w trybie nie-pedantycznym. -pedantic
Jednak przełączenie na sprawia, że wyświetla również odpowiednie ostrzeżenie o dwuznaczności.
Mam nadzieję, że to pomoże nieco wyjaśnić różnice między tymi dwiema formami!
A c1; A c2 = c1; A c3(c1);
.