programowanieProgramista C++

Jakie są różnice między bezpośrednią a pośrednią inicjalizacją obiektów w C++ oraz jakie mogą być tego konsekwencje w pracy z typami wbudowanymi i użytkownikami?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W C++ wyróżnia się bezpośrednią (direct) i pośrednią (copy) inicjalizację obiektów:

  • Bezpośrednia inicjalizacja:

    MyClass obj(arg1, arg2); int x(5);

    Dla klas bezpośrednio wywołuje odpowiedni konstruktor.

  • Pośrednia inicjalizacja:

    MyClass obj = MyClass(arg1, arg2); int x = 5;

    Tworzy obiekt tymczasowy, a następnie go kopiuje (lub przenosi) (używa konstruktora kopiującego/przenoszącego).

Różnice:

  1. Dla typów wbudowanych zachowanie jest identyczne. Dla klas — może być inny zestaw wywołanych konstruktorów lub nawet zablokowana inicjalizacja.
  2. Lista inicjalizacji członków nie zawsze jest równoważna dla różnych metod inicjalizacji z powodu wymagań kompilatora.
  3. Copy elision (optymalizacja kompilatora eliminująca zbędne kopiowanie) często niweluje różnicę (ale nie zawsze, szczególnie przed C++17).

Przykład:

struct NonCopyable { NonCopyable(int) {} NonCopyable(const NonCopyable&) = delete; }; NonCopyable a(5); // OK: bezpośrednie NonCopyable b = NonCopyable(5); // OK z optymalizacją, błąd przed C++17 bez niej NonCopyable c = 5; // Błąd: brak konstruktora kopiowania

Pytanie z haczykiem

"Czy pośrednia inicjalizacja może wywołać więcej wywołań konstruktorów niż bezpośrednia inicjalizacja, jeśli używa się C++11 i włączonego kompilatora optymalizującego?"

Odpowiedź: Teoretycznie — tak. Bez copy elision pośrednia inicjalizacja tworzy obiekt tymczasowy, a następnie wywołuje konstruktor kopiowania lub przenoszenia. Bezpośrednia inicjalizacja zawsze wywołuje tylko główny konstruktor. Niemniej jednak nowoczesne kompilatory z włączoną optymalizacją (C++17 i wyżej) eliminują tę różnicę dzięki guaranteed copy elision.

Przykłady rzeczywistych błędów z powodu braku znajomości szczegółów tematu


Historia

W projekcie finansowym użyto pośredniej inicjalizacji z obiektem tymczasowym, a wymagany typ nie miał konstruktora kopiującego. Aplikacja nie kompilowała się na starych kompilatorach (przed C++17), mimo że direct-init działał.


Historia

W narzędziu do serializacji danych programiści uważali, że direct-init i copy-init są równoważne. W rezultacie występowały zbędne kopie dużych struktur oraz problemy z wydajnością.


Historia

Podczas inicjalizacji globalnych tablic obiektów użytkowników użyto pośredniej inicjalizacji. Błędy pojawiały się tylko na jednej platformie, gdzie copy elision nie zachodziło i występowały niejawne wywołania dodatkowych konstruktorów z powodu specyfiki ABI.