Załóżmy, że mam tę funkcję:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
Czy w każdej grupie te instrukcje są identyczne? Czy może istnieje dodatkowa (możliwa do zoptymalizowania) kopia w niektórych inicjalizacjach?
Widziałem, jak ludzie mówią obie rzeczy. Proszę zacytować tekst jako dowodu. Proszę również dodać inne skrzynki.
c++
initialization
rlbond
źródło
źródło
A c1; A c2 = c1; A c3(c1);
.A
, inicjalizacja kopii nie wymagałaby istnienia konstruktora kopiowania / przenoszenia. Dlategostd::atomic<int> a = 1;
w C ++ 17 jest w porządku, ale nie wcześniej.Odpowiedzi:
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ę (jaka1
), sztuczne obiekty utworzone, gdy inicjalizacja kończy się odrzuceniem, lub jeśli obiekt jest potrzebny do powiązania odwołania (jak, wA_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
ia2
zasad specjalnych, mówi się, że w takich deklaracjach obiekt wynikowy inicjalizatora wartości tego samego typu coa1
zmiennya1
, a zatemA_factory_func()
bezpośrednio inicjuje obiekta1
. Ż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.Zależy, jaki typ
A_factory_func()
zwraca. Zakładam, że zwracaA
- i robi to samo - z wyjątkiem tego, że gdy konstruktor kopii jest jawny, to pierwszy nie powiedzie się. Przeczytaj 8.6 / 14Robi to samo, ponieważ jest to typ wbudowany (w tym przypadku nie jest to typ klasy). Przeczytaj 8.6 / 14 .
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ść doc2
(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 anA
i która przenosi wskaźnik funkcji do funkcji zwracającej aA
(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:
Istnieje zachowanie, które możemy przypisać każdemu z nich:
T
(włączając jeexplicit
), a argumentem jestx
. Rozdzielczość przeciążenia znajdzie najlepszego pasującego konstruktora, a gdy zajdzie taka potrzeba, dokona jakiejkolwiek niejawnej konwersji wymaganej.x
na obiekt typuT
. (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.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 :
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 wpisanaB
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ówZwróć 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ówx
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!
źródło
R() == R(*)()
iT[] == T*
. Oznacza to, że typy funkcji są typami wskaźników funkcji, a typy tablic są typami wskaźników do elementów. To jest do bani. Można to obejśćA c3((A()));
(parens wokół wyrażenia).Przypisanie różni się od inicjalizacji .
Obie poniższe linie wykonują inicjalizację . Wykonywane jest pojedyncze wywołanie konstruktora:
ale nie jest to równoważne z:
W tej chwili nie mam tekstu, aby to udowodnić, ale bardzo łatwo jest eksperymentować:
źródło
double b1 = 0.5;
jest niejawnym wywołaniem konstruktora.double b2(0.5);
jest wyraźnym połączeniem.Spójrz na następujący kod, aby zobaczyć różnicę:
Jeśli twoja klasa nie ma jawnych struktur wewnętrznych, jawne i niejawne wywołania są identyczne.
źródło
Pierwsze grupowanie: zależy od tego, co
A_factory_func
zwraca. Pierwszy wiersz to przykład inicjalizacji kopii , drugi wiersz to bezpośrednia inicjalizacja . JeśliA_factory_func
zwracaA
obiekt, wówczas są one równoważne, oba wywołują konstruktor kopiującyA
, w przeciwnym razie pierwsza wersja tworzy wartość typuA
z dostępnych operatorów konwersji dla typu zwracanegoA_factory_func
lub odpowiednichA
konstruktorów, a następnie wywołuje konstruktor kopiujący, aby skonstruowaća1
z tego chwilowy. Druga wersja próbuje znaleźć odpowiedniego konstruktora, który odbierze dowolneA_factory_func
zwroty lub coś, na co wartość domyślna może zostać niejawnie przekonwertowana.Druga grupa: dokładnie taka sama logika, z tym wyjątkiem, że typy wbudowane nie mają żadnych egzotycznych konstruktorów, więc są w praktyce identyczne.
Trzecie grupowanie:
c1
jest inicjowane domyślnie,c2
jest inicjowane kopiowaniem z wartości zainicjowanej tymczasowo. Dowolni członkowiec1
tego typu pod ((członkowie członków itp. Itp.)) Nie mogą zostać zainicjowani, jeśli użytkownik podał domyślny konstruktor (jeśli taki istnieje) nie inicjuje go jawnie. Ponieważc2
zależy to od tego, czy istnieje dostarczony przez użytkownika konstruktor kopii i czy odpowiednio inicjuje on te elementy, ale wszystkie elementy tymczasowe zostaną zainicjowane (inicjowane zerowo, jeśli inaczej nie zostaną wyraźnie zainicjowane). Jak zauważył litb,c3
jest pułapką. To właściwie deklaracja funkcji.źródło
Nutowy:
[12,2 / 1]
Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
Tj. Do inicjalizacji kopii.
[12,8 / 15]
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
Innymi słowy, dobry kompilator nie utworzy kopii w celu zainicjowania kopii, gdy można tego uniknąć; zamiast tego po prostu wywoła konstruktor bezpośrednio - tj. podobnie jak w przypadku bezpośredniej inicjalizacji.
Innymi słowy, inicjalizacja kopiowania jest jak inicjalizacja bezpośrednia w większości przypadków <opinion>, gdzie napisano zrozumiały kod. Ponieważ bezpośrednia inicjalizacja może potencjalnie powodować dowolne (i dlatego prawdopodobnie nieznane) konwersje, wolę zawsze używać inicjalizacji kopiowania, gdy jest to możliwe. (Z premią, że faktycznie wygląda jak inicjalizacja.) </opinion>
Techniczna gorliwość: [12,2 / 1 cd z góry]
Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Cieszę się, że nie piszę kompilatora C ++.
źródło
Można zobaczyć jego różnicę
explicit
iimplicit
rodzajów konstruktorów podczas inicjalizacji obiektu:Klasy:
I w
main
funkcji:Domyślnie konstruktor jest
implicit
taki, że można go zainicjować na dwa sposoby:A poprzez zdefiniowanie struktury jako
explicit
tylko jeden sposób jest bezpośredni:źródło
Odpowiedzi dotyczące tej części:
Ponieważ większość odpowiedzi jest wcześniejszych niż c ++ 11, dodaję co c ++ 11 ma do powiedzenia na ten temat:
Optymalizacja, czy nie, są równoważne zgodnie ze standardem. Pamiętaj, że jest to zgodne z tym, o czym wspominały inne odpowiedzi. Wystarczy zacytować to, co norma ma do powiedzenia dla zachowania poprawności.
źródło
Wiele z tych przypadków podlega implementacji obiektu, więc trudno jest podać konkretną odpowiedź.
Rozważ przypadek
W tym przypadku, zakładając właściwy operator przypisania i konstruktor inicjujący, który akceptuje pojedynczy argument liczby całkowitej, sposób, w jaki implementuję wspomniane metody, wpływa na zachowanie każdej linii. Jednak powszechną praktyką jest wywoływanie jednego z nich w implementacji w celu wyeliminowania duplikatu kodu (chociaż w tak prostym przypadku nie byłoby to prawdziwym celem).
Edycja: Jak wspomniano w innych odpowiedziach, pierwszy wiersz w rzeczywistości wywoła konstruktora kopiowania. Rozważ komentarze dotyczące operatora przypisania jako zachowania dotyczące samodzielnego przypisania.
To powiedziawszy, jak kompilator zoptymalizuje kod, będzie miał wtedy swój własny wpływ. Jeśli mam konstruktor inicjujący wywołujący operator „=” - jeśli kompilator nie dokonuje optymalizacji, górna linia wykona następnie 2 skoki w przeciwieństwie do jednego w dolnym wierszu.
Teraz, w najczęstszych sytuacjach, twój kompilator zoptymalizuje te przypadki i wyeliminuje tego rodzaju nieefektywności. W efekcie wszystkie opisane sytuacje okażą się takie same. Jeśli chcesz dokładnie zobaczyć, co się dzieje, możesz spojrzeć na kod obiektu lub dane wyjściowe zestawu kompilatora.
źródło
operator =(const int)
i nieA(const int)
. Aby uzyskać więcej informacji, zobacz odpowiedź @ jia3ep.Pochodzi z języka programowania C ++ autorstwa Bjarne Stroustrup:
Inicjalizacja za pomocą = jest uważana za inicjalizację kopii . Zasadniczo kopia inicjalizatora (obiekt, z którego kopiujemy) jest umieszczana w inicjowanym obiekcie. Jednak taka kopia może być zoptymalizowana (pomijana), a operacja przenoszenia (oparta na semantyce ruchu) może być użyta, jeśli inicjalizatorem jest wartość. Pozostawienie = powoduje, że inicjalizacja jest jawna. Jawna inicjalizacja jest znana jako bezpośrednia inicjalizacja .
źródło