Nowy standard ISO C++ (C++11) – krótkie wprowadzenie


Nie pisałem nic tutaj od dawna. To sam w sobie dobry pretekst, aby otworzyć to okno i zacząć pisać. Tym razem nie będę się tłumaczył, bo co miałbym napisać, praca.. konferencje, życie?

Przejdę zatem do sedna i spróbuję tak skrótowo opisać coś co ponownie i po wielu latach stało się moją wielką pasją, czyli nazwijmy sobie.. takie ćwiczenie hobbistyczne doskonałości kodu w C++.
Kiedyś, jeszcze za młodzieńczych czasów demoscenowych, wspólnie ze znajomymi z którymi w tej społeczności się rozwijaliśmy, potrafiliśmy tygodniami siedzieć nad małymi fragmentami kodu i upojnie zastanawiać się jak pewne rzeczy można zrobić lepiej. Zmiana a raczej dalsza ewolucja standardu języka C++ dostarczyła impulsu potrzebnego, aby ponownie zacząć zadawać sobie z pozoru głupie pytania. Z drugiej strony jednak.. jeśli komuś zależy na pełnej kontroli tego co nazywa kodem, na pewno na wiele rzeczy w nowym C++ już zdążył zwrócić uwagę. Jeśli nie to pozwolę sobie taki artykuł wysmarować jako start do pewnej dyskusji...

Drobne zmiany składniowe

Zacznijmy od drobnostek takich jak chociażby auto. Przez wiele lat programowania w C#/.NET Framework przywykłem do tego, że mam coś takiego jak var. Dla wygody szybkiego programowania, kiedy to pozwalamy sobie na zaufanie dla kompilatora, który z łatwością odgadnie jaki nazwany typ ma zastosować przy uruchomieniu naszego kodu, var jest bezcenne. Ale właśnie.. w C# i przynajmniej przy moich mocno uproszczonych, ale i mocno czytelnych technikach projektowania obiektowego ten var to tak naprawdę element lenistwa, który mi koledzy z Redmond dostarczyli. Intellisense sam w sobie był przez te wszystkie lata wystarczającym mechanizmem, aby odpowiedni typ w odpowiedniej linijce kodu się po prostu znalazł po kilku wpisanych znakach. Trzech... czterech, jak auto czy var

W C++ zaś auto, pełniący podobną funkcje odbierany jest przeze mnie o wiele mocniej. Zwłaszcza przy predykatach, iteratorach czy innych tymczasowo przetrzymywanych obietkach pochodzących z szablonowych obiektów chociażby biblioteki STL. Deklarowanie typu dla tworzonego w locie wyrażenia Lambda... auto, auto, auto...

Wyrażenia Lambda pominę w tym wpisie. Poświęciłem tej koncepcji kilka wpisów lata temu. Wróćmy do prostych, acz sympatycznych rzeczy.

(unsigned) long long zamiast __int64

Niby drobnostka, ale spójna. Zwłaszcza, że wg definicji standardu long long to nie stricte 64 bity tylko maksymalna wielkość liczby całkowitej jaką jest w stanie obsłużyć architektura CPU.
Po co..? Dla optymalizacji obliczeń matematycznych zgodnych z architekturą komputera na którym pracujemy. Zakładam, że jak kiedyś do nas dojdą komputery 128 bitowe to ta sama deklaracja

long long a = 1; 
long long b = 2;
long long c = a+b;

wygeneruje nam odpowiednie rozkazy opierające się na bieżącej architekturze CPU.

Nie chcemy wiedzieć, czy to na koniec jest: 

mov eax, 1
mov ebx, 2
add eax, ebx
mov ecx, eax

czy cokolwiek innego..

Chcemy wiedzieć, że jak możliwości matematyczne komputerów się zwiększą, a zależy nam na maksymalnym zasięgu liczbowym, to nie będziemy musieli robić refactoringu.
Wystarczy teraz określić odpowiedni typ, który jest zgodny ze standardem, czyli powinien być zaimplementowany przez każdy kompilator, który jest z nim zgodny. 

Na dzień dzisiejszy dyskusja jest mocno teoretyczna. Zawarta jest jednak w definicji samego standardu: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1811.pdf 

NULL

Pominę długie tłumaczenie wprowadzenie kolejnego słowa kluczowego, nullptr. Dla kogoś kto chciał rozróżnić wartość 0L od NULL, który zwykle wszędzie byl definiowany jako #define NULL 0L sens powinien się właśnie ujawnić.

RValues oraz LValues

Trudne hasła, bo jeśli nie do końca rozumiemy mechanikę języka, to zwykle tego typu nazwy widzimy w komunikatach błędów kompilatora.
Bardzo dobre wyjaśnienie do tej części dostarcza stary już wpis teamu odpowiedzialnego za Visual C++:
http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

W praktyce, po zmianach w standardzie, nie powinniście mieć problemu chociażby z kompilacją czegoś takiego:

#include <iostream>
#include <string>

using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Dodatkowym efektem wprowadzenia zmian związanych z przekazywaniem referencji do tymczasowych obiektów jest konstruktor przeniesienia (move constructor).
Dzięki niemu za pomocą prostej deklaracji i kodu, który za nią pójdzie, obok konstruktora kopiującego, możemy zadeklarować takowy:

SimplestMoveableObject(SimplestMoveableObject&& moveSource)

i/lub operator przypisania takowy:

SimplestMoveableObject& operator=(SimplestMoveableObject&& moveSource)

i wiele z operacji, które reprezentują nasz kod mają szansą odbyć się bez zbędnego kopiowania dużej ilości danych, które często nasze obiekty w sobie kryją.

Ach te szablony!

Na koniec jeszcze kilka zdań na temat static_assert. Sprzed zmiany standardu mieliśmy oczywiście makro ASSERT oraz dyrektywę #error.

Feller niestety polega na tym, że słabo się przydają przy szablonowych klasach (templates). Dlaczego? 
Odpowiedź jest prosta.. klasyczny ASSERT zrzuca błędy po uruchomieniu, natomiast prawdziwy vs. deklarowany kształt obiektów szablonowych widzimy dopiero na etapie kompilacji.
Przydałoby się narzędzie, które na etapie kompilacji szablonów pozwoliło by nam wychwycić możliwe nieprawidłowości.
Do tego właśnie służy static_assert!

Dobry przykład z pierwszej części wspomnianego przeze artykułu na vcblog

template <int N> struct Kitten {
    static_assert(N < 2, "Kitten<N> requires N < 2.");
}; 

int main() {
    Kitten<1> peppermint; 
    Kitten<3> jazz;
}

Jeśli są pewne błędy w szablonie wynikające z nieprawidłowego podania parametrów do niego, static_assert pomaga je sprawdzić i zakomunikować.

To tylko pierwsze przykłady, drobnych ale i mocnych zmian. Jak widać po referencyjnych blogach, wcale nie od dzisiaj zaimplementowane w Visual C++.
Wierzę jednak, że przez fakt, że poza tematem gier C++ w środowisku Windows wcale nie musiało być tak często wykorzystywane, te rzeczy mogły być przez wielu z was niezauważone.
W dobie Windows 8 i mocnego wsparcia dla C++ przy tworzeniu aplikacji Metro, temat C++ wraca. Wraz z nim temat możliwie najlepszych praktyk w programowaniu w języku, który nie jest łatwy, ale jak się go opanuje, to dostarcza chyba największej frajdy z kontroli tego co się wokół dzieje.

Temat nowego standardu jest oczywiście o wiele szerszy. Zmiany dotyczą zarówno samego składnika, jak i biblioteki standardowej (STL).
Kolejnym dobrym punktem startu w tym temacie jest chociażby tabelka zamieszczona na tym wpisie:
http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx 

Zawiera w miarę kompletną informację o tym, jak przy okazji przemiery VS11, do wsparcia dla C++11 podchodzi ekipa Visual C++.

pozdrawiam po dłuższej przerwie,
Daniel Biesiada 

Comments (0)

Skip to main content