Nowe słowo kluczowe = default w C++11

Nie rozumiem, po co miałbym to robić:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Dlaczego po prostu nie powiedzieć:

S() {} // instead of S() = default;

Po co wprowadzać do tego nowe słowo kluczowe?

 98
Author: user3111311, 2013-12-29

3 answers

Konstruktor domyślny jest definiowany jako taki sam jak konstruktor domyślny zdefiniowany przez Użytkownika bez listy inicjalizacyjnej i pustej instrukcji złożonej.

§12.1/6 [klasy.ctor] domyślny konstruktor, który jest domyślny i nie zdefiniowany jako usunięty, jest domyślnie zdefiniowany, gdy jest używany do tworzenia obiektu typu klasy lub gdy jest jawnie domyślny po pierwszej deklaracji. Domyślnie zdefiniowany konstruktor domyślny wykonuje zbiór inicjalizacji klasy, które byłyby wykonywane przez domyślny konstruktor napisany przez użytkownika dla tej klasy bez inicjalizacji ctor (12.6.2) i pustej instrukcji złożonej. [...]

Jednakże, podczas gdy oba konstruktory zachowają się tak samo, podanie pustej implementacji wpływa na niektóre właściwości klasy. Podanie konstruktora zdefiniowanego przez użytkownika, mimo że nic nie robi, sprawia, że typ nie jest zbiorczy , a także nie trywialny . Jeśli chcesz swój Klasa aby była zbiorcza lub trywialna (lub przez przechodniość, Typ POD), musisz użyć = default.

§8.5.1/1 [dcl.init.aggr] Agregat jest tablicą lub klasą bez konstruktorów dostarczonych przez użytkownika, [and...]

§12.1/5 [klasy.ctor] konstruktor domyślny jest trywialny, jeśli nie jest dostarczany przez użytkownika i [...]

§9/6 [class] Klasa trywialna jest klasą, która ma trywialny konstruktor domyślny i [...]

Aby zademonstrować:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");

    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

Dodatkowo, jawnie niewykonanie konstruktora sprawi, że constexpr jeśli konstruktor implicit byłby, a także poda mu tę samą specyfikację WYJĄTKÓW, którą konstruktor implicit miałby. W takim przypadku konstruktor niejawny nie byłby constexpr (ponieważ pozostawiałby element danych niezainicjowany), a także miałby pustą specyfikację WYJĄTKÓW, więc nie ma żadnej różnicy. Ale tak, w ogólny przypadek można ręcznie określić constexpr i specyfikację wyjątku, aby pasowała do niejawnego konstruktora.

Użycie = default przynosi pewną jednolitość, ponieważ może być również używane z konstruktorami Kopiuj / przenieś i destruktorami. Pusty Konstruktor kopiujący, na przykład, nie zrobi tego samego, co domyślny Konstruktor kopiujący (który wykona kopię składową swoich członków). Używanie składni = default (lub = delete) równomiernie dla każdej z tych specjalnych funkcji Członkowskich ułatwia kod do przeczytania, wyraźnie stwierdzając swój zamiar.

 114
Author: Joseph Mansfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-16 15:02:04

N2210 podaje kilka powodów:

Zarządzanie domyślnymi ma kilka problemów:

  • definicje konstruktorów są sprzężone; deklarowanie dowolnego konstruktora powoduje usunięcie konstruktora domyślnego.
  • wartość domyślna destruktora jest nieodpowiednia dla klas polimorficznych, wymagających jednoznacznej definicji.
  • gdy wartość domyślna zostanie stłumiona, nie ma możliwości jej wskrzeszenia.
  • domyślne implementacje są często bardziej wydajne niż ręczne określone implementacje.
  • implementacje niestandardowe są nietrywialne, co wpływa na semantykę typu, np. sprawia, że typ nie-POD.
  • nie ma sposobu, aby zabronić specjalnej funkcji członka lub operatora globalnego bez zadeklarowania (nietrywialnego) substytutu.

type::type() = default;
type::type() { x = 3; }

W niektórych przypadkach, ciało klasy może się zmieniać bez konieczności zmiany definicji funkcji członka, ponieważ domyślne zmiany za pomocą deklaracja o dodatkowych członków.

Zobacz Rule-of-Three staje się regułą-of-Five w C++11?:

Zauważ, że konstruktor move i operator przypisania move nie będą generowane dla klasy, która jawnie deklaruje jakąkolwiek inną Funkcje Specjalne, że Konstruktor kopiujący i przypisanie kopiowania operator nie zostanie wygenerowany dla klasy, która jawnie deklaruje a move constructor lub move assignment operator, i że klasa z jawnie zadeklarowany Destruktor i niejawnie zdefiniowany Konstruktor kopiujący lub w sposób niejawny zdefiniowany operator przypisania kopii jest uważany deprecated

 9
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:15

To kwestia semantyki w niektórych przypadkach. Nie jest to zbyt oczywiste w przypadku domyślnych konstruktorów, ale staje się oczywiste w przypadku innych funkcji składowych generowanych przez kompilator.

Dla domyślnego konstruktora, możliwe byłoby, aby każdy domyślny konstruktor z pustym ciałem był uważany za kandydata na trywialny konstruktor, tak samo jak użycie =default. W końcu starymi pustymi domyślnymi konstruktorami były legalne C++.

struct S { 
  int a; 
  S() {} // legal C++ 
};

Czy kompilator rozumie ten konstruktor jako trywialny jest nieistotny w większości przypadków poza optymalizacjami (ręcznymi lub kompilatorowymi).

Jednak ta próba traktowania pustych ciał funkcyjnych jako "domyślnych" załamuje się całkowicie dla innych typów funkcji członowych. Rozważmy Konstruktor kopiujący:

struct S { 
  int a; 
  S() {}
  S(const S&) {} // legal, but semantically wrong
};

W powyższym przypadku Konstruktor kopiujący napisany z pustym ciałem jest teraz błędny. To już nie kopiuje niczego. Jest to zupełnie inny zestaw semantyki niż domyślna semantyka konstruktora kopiującego. Pożądane zachowanie wymaga napisania kodu:

struct S { 
  int a; 
  S() {}
  S(const S& src) : a(src.a) {} // fixed
};

Nawet w tym prostym przypadku, staje się jednak znacznie większym obciążeniem dla kompilatora, aby zweryfikować, czy Konstruktor kopiujący jest identyczny z tym, który sam wygeneruje, lub dla niego, aby zobaczyć, że Konstruktor kopiujący jest trywialny (odpowiednik memcpy, W zasadzie). Kompilator musiałby sprawdzić każde wyrażenie inicjalizatora i upewnić się, że jest identyczne z wyrażenie aby uzyskać dostęp do odpowiadającego mu elementu źródłowego i nic więcej, upewnij się, że żadne elementy nie są pozostawione z nietrywialną domyślną konstrukcją, itp. Jest wsteczny w taki sposób, jak proces, którego kompilator użyłby do sprawdzenia, czy własne wygenerowane wersje tej funkcji są trywialne.

Rozważ więc operator przypisania kopii, który może stać się jeszcze bardziej hairier, zwłaszcza w nietrywialnym przypadku. To jest tona kotła-Płyta, że nie chcesz pisać na wiele klas, ale jesteś be w C++03:

struct T { 
  std::shared_ptr<int> b; 
  T(); // the usual definitions
  T(const T&);
  T& operator=(const T& src) {
    if (this != &src) // not actually needed for this simple example
      b = src.b; // non-trivial operation
    return *this;
};

To prosty przypadek, ale to już więcej kodu, niż chciałbyś być zmuszony do pisania dla tak prostego typu jak T (zwłaszcza gdy wrzucimy operacje move do miksu). Nie możemy polegać na pustym ciele oznaczającym "wypełnij wartości domyślne", ponieważ puste ciało jest już całkowicie poprawne i ma jasne znaczenie. W rzeczywistości, gdyby puste ciało zostało użyte do oznaczenia "wypełnij wartości domyślne" , nie byłoby sposobu na jawne wykonanie kopii bez operacji konstruktor lub tym podobne.

To znowu kwestia spójności. Puste ciało oznacza" nic nie robić", ale w przypadku takich rzeczy jak konstruktory kopiujące naprawdę nie chcesz "nic nie robić", ale raczej " robić wszystkie rzeczy, które normalnie byś zrobił, gdyby nie było tłumione."Stąd =default. Jest to niezbędne do przezwyciężania tłumionych funkcji składowych generowanych przez kompilator, takich jak konstruktory kopiowania/przenoszenia i operatory przypisywania. To jest po prostu "oczywiste", aby to działało dla domyślnego konstruktora jako cóż.

Byłoby miło sprawić, aby domyślny konstruktor z pustymi ciałami i trywialnymi konstruktorami prętowymi / bazowymi również był uważany za trywialny, tak jak w przypadku =default, gdyby tylko w niektórych przypadkach starszy kod był bardziej optymalny, ale większość niskopoziomowych kodów opartych na trywialnych domyślnych konstruktorach do optymalizacji również opiera się na trywialnych konstruktorach kopiujących. Jeśli masz zamiar iść i "naprawić" wszystkie swoje stare konstruktory kopiujące, to naprawdę nie jest zbyt wiele rozciągnięcia, aby naprawić wszystkie twoje stare domyślne konstruktory też. Jest to również znacznie jaśniejsze i bardziej oczywiste, używając wyraźnego =default, Aby określić swoje intencje.

Jest kilka innych rzeczy, które będą robić funkcje Członkowskie generowane przez kompilator, a także będziesz musiał jawnie wprowadzić zmiany do obsługi. Jednym z przykładów jest wsparcie constexpr dla domyślnych konstruktorów. Jest po prostu łatwiejsze mentalnie używać =default niż oznaczyć funkcje wszystkimi innymi specjalnymi słowami kluczowymi i takimi, które są implikowane przez =default i że był jednym z tematów C++11: make the language easier. Nadal ma mnóstwo brodawek i kompromisy, ale jasne jest, że jest to duży krok naprzód od C++03, jeśli chodzi o łatwość użycia.

 3
Author: Sean Middleditch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-31 21:53:48