Które idiomy C ++ są przestarzałe w C ++ 11?

192

Dzięki nowemu standardowi istnieją nowe sposoby robienia rzeczy, a wiele z nich jest ładniejszych niż stare sposoby, ale stary sposób jest nadal w porządku. Oczywiste jest również, że nowy standard nie jest oficjalnie bardzo przestarzały, ze względu na kompatybilność wsteczną. Pozostaje więc pytanie:

Jakie stare sposoby kodowania są zdecydowanie gorsze od stylów C ++ 11 i co możemy teraz zamiast tego zrobić?

Odpowiadając na to, możesz pominąć oczywiste rzeczy, takie jak „użyj zmiennych automatycznych”.

Alan Baljeu
źródło
13
Nie można wycofać idiomów.
Pubby
6
Przemówienie Herb Suttera na Going Native 2012 dotyczyło:
bames53
5
Zwracanie stałych wartości nie jest już zalecane. Oczywiście auto_ptrjest również przestarzałe.
Kerrek SB,
27
Oczywiście, że możesz, Pubby. Przed wynalezieniem szablonów C ++ istniała technika makr do tworzenia szablonów. Następnie C ++ je dodał i stary sposób został uznany za zły.
Alan Baljeu
7
To pytanie naprawdę musi zostać przeniesione do Programmers.se.
Nicol Bolas,

Odpowiedzi:

173
  1. Klasa końcowa : C ++ 11 zapewnia finalspecyfikator zapobiegający wyprowadzaniu klas
  2. Lambda w C ++ 11 znacznie zmniejszają potrzebę stosowania nazwanych klas obiektów funkcyjnych (funktorów).
  3. Move Constructor : Magiczne sposoby, w jakie std::auto_ptrprace nie są już potrzebne ze względu na pierwszorzędne wsparcie dla odniesień do wartości.
  4. Bezpieczny bool : wspomniano wcześniej. Jawne operatory C ++ 11 eliminują ten bardzo popularny idiom C ++ 03.
  5. Dopasuj do siebie : Wiele kontenerów STL C ++ 11 zapewnia funkcję shrink_to_fit()elementu członkowskiego, co powinno wyeliminować potrzebę tymczasowej zamiany.
  6. Tymczasowa klasa podstawowa : niektóre stare biblioteki C ++ używają tego dość złożonego idiomu. Z semantyką ruchu nie jest już potrzebny.
  7. Wyliczenia typu Bezpieczne wyliczanie są bardzo bezpieczne w C ++ 11.
  8. Zakaz alokacji sterty : = deleteSkładnia jest znacznie bardziej bezpośrednim sposobem stwierdzenia, że ​​konkretna funkcjonalność jest wyraźnie zabroniona. Ma to zastosowanie do zapobiegania przydziałowi stosu (tj. =deleteDla członka operator new), zapobiegania kopiowaniu, przypisaniu itp.
  9. Typedef szablony : szablony Alias w C ++ 11 zmniejszają potrzebę stosowania prostych szablonów typedef. Jednak generatory typu złożonego nadal potrzebują meta funkcji.
  10. Niektóre numeryczne obliczenia w czasie kompilacji, takie jak Fibonacciego, można łatwo zastąpić za pomocą uogólnionych wyrażeń stałych
  11. result_of: Zastosowania szablonu klasy result_ofnależy zastąpić decltype. Myślę, że result_ofużywa, decltypegdy jest dostępny.
  12. Inicjatory elementów w klasie zapisują typowanie dla domyślnej inicjalizacji elementów niestatycznych z wartościami domyślnymi.
  13. W nowym C ++ 11 kod NULLpowinien zostać ponownie zdefiniowany jako nullptr, ale zobacz przemówienie STL, aby dowiedzieć się, dlaczego zdecydowali się na to.
  14. Fanatycy szablonów wyrażeń są zachwyceni, że w C ++ 11 ma końcową składnię funkcji typu powrotu . Nigdy więcej 30-liniowych długich zwrotów!

Myślę, że się tam zatrzymam!

Sumant
źródło
Dzięki za szczegółowe informacje!
Alan Baljeu
7
Świetna odpowiedź, ale wybiłbym result_ofz listy. Mimo, że typenamebyło to uciążliwe przedtem, myślę, że typename result_of<F(Args...)::typeczasami może być łatwiejsze do odczytania niż decltype(std::declval<F>()(std::declval<Args>()...), a po przyjęciu N3436 do dokumentu roboczego, oboje pracują dla SFINAE (co było zaletą, decltypeże result_ofnie oferowało)
Jonathan Wakely
Odnośnie 14) Nadal płaczę, że muszę użyć makr, aby dwukrotnie napisać ten sam kod - raz dla treści funkcji i raz dla instrukcji decltype () ...
2
Chciałbym zauważyć, że ten temat jest linkowany ze strony Microsoft jako artykuł „Aby uzyskać więcej informacji” w ogólnym wprowadzeniu do języka C ++, ale ten temat jest wysoce specjalistyczny! Czy mogę zasugerować krótkie „Ten temat NIE jest dla nowicjuszy C ++!” porady na początku tematu lub odpowiedź?
Aacini,
Re 12: „Inicjalizacja elementu w klasie” - to nowy idiom, a nie przestarzały idiom, prawda? Może zmienić kolejność zdań? Re 2: Funkcje są bardzo przydatne, gdy chcesz przekazywać typy zamiast obiektów (szczególnie w parametrach szablonu). Więc tylko niektóre zastosowania funktorów są przestarzałe.
einpoklum,
66

W pewnym momencie argumentowano, że należy zwracać constwartość, a nie tylko wartość:

const A foo();
^^^^^

Było to w większości nieszkodliwe w C ++ 98/03 i mogło nawet wykryć kilka błędów, które wyglądały następująco:

foo() = a;

Ale powrót przez constjest przeciwwskazany w C ++ 11, ponieważ hamuje semantykę ruchu:

A a = foo();  // foo will copy into a instead of move into it

Po prostu zrelaksuj się i napisz:

A foo();  // return by non-const value
Howard Hinnant
źródło
9
Można jednak teraz uchwycić błędy, których można uniknąć, używając kwalifikatorów referencyjnych dla funkcji. Tak jak w powyższym przypadku A& operator=(A o)&zamiast A& operator=(A o). Zapobiegają głupim błędom i sprawiają, że klasy zachowują się bardziej jak podstawowe typy i nie zapobiegają semantyce ruchów.
Joe
61

Jak najszybciej możesz porzucić 0i NULLna korzyść nullptr, zrób to!

W kodzie nieogólnym użycie 0lub NULLwcale nie jest takie duże. Ale gdy tylko zaczniesz przekazywać wartości stałe wskaźnika zerowego w kodzie ogólnym, sytuacja szybko się zmienia. Gdy przejdziesz 0do a, template<class T> func(T) Tzostanie wydedukowane jako intstała zerowa, a nie jako stała zerowa. Po tym nie można go przekształcić z powrotem w stałą wskaźnika zerowego. Spada to w bagno problemów, które po prostu nie istnieją, gdyby tylko wszechświat go używał nullptr.

C ++ 11 nie jest przestarzałe 0i NULLjako stałe wskaźnika zerowego. Ale powinieneś kodować tak, jakby to zrobił.

Howard Hinnant
źródło
co to jest decltype (nullptr)?
4
@GrapschKnutsch: It is std::nullptr_t.
Howard Hinnant,
Zaproponuj, aby sformułować to jako przestarzałe idiom, a nie nową konwencję do przyjęcia (np. „Zastosowanie wskaźników zerowych” 0lub „ NULLdla zerowych”).
einpoklum
38

Bezpieczny idol boolexplicit operator bool().

Konstruktory kopii prywatnych (boost :: noncopyable) → X(const X&) = delete

Symulowanie końcowej klasy za pomocą prywatnego destruktora i wirtualnego dziedziczeniaclass X final

kennytm
źródło
dobre i zwięzłe przykłady, z których jeden zawiera nawet słowo „idiom”. dobrze postawiony
Sebastian Mach
2
Wow, nigdy wcześniej nie widziałem „bezpiecznego idiomu bool”, wygląda dość obrzydliwie! Mam nadzieję, że nigdy go nie potrzebuję w kodzie wcześniejszym niż C ++ 11 ...
boycy
24

Jedną z rzeczy, które po prostu unikają pisania podstawowych algorytmów w C ++ 11, jest dostępność lambd w połączeniu z algorytmami zapewnianymi przez standardową bibliotekę.

Używam ich teraz i to niewiarygodne, jak często mówisz, co chcesz zrobić, używając count_if (), for_each () lub innych algorytmów zamiast pisać ponownie cholerne pętle.

Kiedy używasz kompilatora C ++ 11 z pełną biblioteką standardową C ++ 11, nie masz już żadnej wymówki, by nie używać standardowych algorytmów do budowania własnych . Lambda po prostu go zabij.

Czemu?

W praktyce (po tym, jak sam użyłem tego sposobu pisania algorytmów), znacznie łatwiej jest przeczytać coś, co jest zbudowane z prostych słów oznaczających to, co się dzieje, niż z niektórymi pętlami, które musisz odszyfrować, aby poznać znaczenie. To powiedziawszy, automatyczne obliczanie argumentów lambda bardzo pomogłoby uczynić składnię łatwiejszą do porównania z surową pętlą.

Zasadniczo algorytmy odczytu wykonane przy użyciu standardowych algorytmów są znacznie łatwiejsze, ponieważ słowa ukrywające szczegóły implementacji pętli.

Zgaduję, że należy teraz myśleć tylko o algorytmach wyższego poziomu, ponieważ mamy algorytmy niższego poziomu do zbudowania.

Klaim
źródło
8
Właściwie istnieje dobra wymówka. Używasz Boost.Range za algorytmy, które są o wiele ładniejsze;)
Nicol Bolas
10
Nie widzę, aby for_eachz lambda była lepsza niż równoważna pętla oparta na zakresie, z zawartością lambda w pętli. Kod wygląda mniej więcej tak samo, ale lambda wprowadza dodatkową interpunkcję. Możesz użyć ekwiwalentów rzeczy, takich jak boost::irangezastosowanie go do większej liczby pętli niż tylko te, które oczywiście używają iteratorów. Ponadto pętla oparta na zakresie ma większą elastyczność, dzięki czemu możesz wyjść wcześniej, jeśli jest to wymagane (przez returnlub przez break), podczas gdy for_eachmusisz rzucić.
Steve Jessop,
5
@SteveJessop: Mimo to dostępność opartego na zakresie forpowoduje, że zwykły it = c.begin(), const end = c.end(); it != end; ++itidiom nie działa .
Ben Voigt,
7
@ SteveJessop Jedną z zalet for_eachalgorytmu w stosunku do zakresu opartego na pętli jest to, że nie możesz break lub return. Oznacza to, że kiedy widzisz for_each, natychmiast wiesz, nie patrząc na ciało, że nie ma takiej podstępu.
bames53
5
@Klaim: żeby być konkretnym, porównuję na przykład std::for_each(v.begin(), v.end(), [](int &i) { ++i; });z for (auto &i : v) { ++i; }. Akceptuję, że elastyczność jest obosieczna ( gotojest bardzo elastyczna, to jest problem). Nie sądzę, że ograniczenie wynikające z niemożności użycia breakw for_eachwersji rekompensuje dodatkową gadatliwość, jakiej wymaga - for_eachtutaj użytkownicy IMO poświęcają rzeczywistą czytelność i wygodę dla pewnego rodzaju teoretycznego pojęcia, że for_eachjest to w zasadzie jaśniejsze i koncepcyjnie prostsze. W praktyce nie jest to ani jaśniejsze ani prostsze.
Steve Jessop,
10

Będziesz musiał wdrażać niestandardowe wersje swaprzadziej. W C ++ 03 swapczęsto konieczne jest wydajne nie rzucanie, aby uniknąć kosztownych i rzucających kopii, a ponieważ std::swapużywa dwóch kopii, swapczęsto trzeba je dostosować. W C ++ std::swapużywa move, więc skupiono się na wdrażaniu wydajnych i nie rzucających konstruktorów ruchów i operatorów przypisań ruchów. Ponieważ dla tych wartości domyślnych często jest dobrze, będzie to o wiele mniej pracy niż w C ++ 03.

Zasadniczo trudno jest przewidzieć, które idiomy zostaną użyte, ponieważ są tworzone przez doświadczenie. Możemy spodziewać się „Skutecznego C ++ 11” może w przyszłym roku, a „Standardów kodowania C ++ 11” tylko za trzy lata, ponieważ nie ma jeszcze niezbędnego doświadczenia.

Philipp
źródło
1
Wątpię w to. Zalecanym stylem jest użycie swap do przenoszenia i kopiowania konstrukcji, ale nie std :: swap, ponieważ byłby okrągły.
Alan Baljeu
Tak, ale konstruktor ruchu zwykle wywołuje niestandardową zamianę lub jest zasadniczo równoważny.
Odwrotny
2

Nie znam jego nazwy, ale kod C ++ 03 często używał następującej konstrukcji jako zamiennika brakującego przypisania ruchu:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Pozwoliło to uniknąć kopiowania z powodu usunięcia kopii w połączeniu z swappowyższym.

Andrzej
źródło
1
W twoim przykładzie zamiana nie jest konieczna, usunięcie kopii i tak skonstruuje wartość zwracaną map. Technika, którą pokazujesz, jest przydatna, jeśli mapjuż istnieje, a nie tylko w jej konstrukcji. Przykład byłby lepszy bez komentarza „tani domyślny konstruktor” i z „// ...” pomiędzy tą konstrukcją a zamianą
Jonathan Wakely
Zmieniłem to zgodnie z twoją sugestią. Dzięki.
Andrzej
Użycie „dużych” i „większych” jest mylące. Dlaczego nie wyjaśnić, jak ważne są rozmiary klucza i typ wartości?
einpoklum
1

Kiedy zauważyłem, że kompilator korzystający ze standardu C ++ 11 nie powoduje już błędu następującego kodu:

std::vector<std::vector<int>> a;

za rzekomo zawierający operator >> zacząłem tańczyć. We wcześniejszych wersjach trzeba było to zrobić

std::vector<std::vector<int> > a;

Co gorsza, jeśli kiedykolwiek musiałeś to debugować, wiesz, jak przerażające są komunikaty o błędach, które z tego wynikają.

Nie wiem jednak, czy było to dla ciebie „oczywiste”.

v010dya
źródło
1
Ta funkcja została dodana już w poprzednim C ++. Lub przynajmniej Visual C ++ zaimplementował go zgodnie ze standardami wiele lat wcześniej.
Alan Baljeu
1
@AlanBaljeu Oczywiście istnieje wiele niestandardowych rzeczy dodawanych do kompilatora / bibliotek. Było mnóstwo kompilatorów, które miały deklarację zmiennych „auto” przed C ++ 11, ale wtedy nie mogłeś być pewien, że twój kod może zostać skompilowany przez cokolwiek innego. Pytanie dotyczyło standardu, a nie „czy był jakiś kompilator, który mógłby to zrobić”.
v010dya
1

Zwrot według wartości nie stanowi już problemu. Z semantyką ruchu i / lub optymalizacją wartości zwracanych (zależnych od kompilatora) funkcje kodowania są bardziej naturalne, bez kosztów ogólnych i kosztów (przez większość czasu).

Martin A.
źródło
... ale który idiom jest przestarzały?
einpoklum
To nie idiom, ale była to dobra praktyka, która nie była już potrzebna. Nawet z RVO obsługiwanym przez kompilator, który jest opcjonalny. en.wikipedia.org/wiki/Return_value_optimization „We wczesnych etapach ewolucji C ++ niezdolność języka do skutecznego zwracania obiektu typu klasy z funkcji była uważana za słabość .....” struct Data {char bytes [ 16]; }; void f (Dane * p) {// wygeneruj wynik bezpośrednio w * p} int main () {Data d; f (i d); }
Martin,
Sugerowałem, że powinieneś sformułować swoją odpowiedź, ponieważ „zwyczaj unikania zwrotu według wartości nie jest już istotny, ponieważ itp. Itd.”
einpoklum,