C++11
C++11 (znany również jako C++0x) – trzecie wydanie standardu języka programowania C++ opublikowane we wrześniu 2011 r. i zastępujące poprzednią edycję standardu zwaną C++03 z 2003 r. W edycji C++11 wprowadzono kilka dodatków do rdzenia języka oraz znacznie rozszerzono bibliotekę standardową C++, m.in. o biblioteki zawarte w Raporcie Technicznym 1 z wyjątkiem biblioteki matematycznych funkcji specjalnych.
Podobnie jak w poprzednich wydaniach, język C++ jest standardem ISO/IEC opublikowanym jako kolejna edycja serii ISO/IEC 14882 pod nazwą „ISO/IEC 14882:2011”[1][2].
Od czasu wydania wersji C++11 opracowane zostały dwa kolejne standardy: C++14 (15 grudnia 2014)[3] oraz C++17 (w grudniu 2017)[4]. Obecnie trwają prace nad standardem C++20.
Zmiany w nowym standardzie
Jak wspomniano, modyfikacje C++ obejmują rdzeń języka oraz jego biblioteki standardowe. W trakcie rozwoju nowego standardu, Komitet zastosował następujące dyrektywy:
- Utrzymywać stabilność i kompatybilność z C++98 i być może z C,
- Preferować wprowadzanie nowych możliwości przez rozszerzenie biblioteki standardowej zamiast rozszerzenia rdzenia języka,
- Preferować zmiany mogące rozwijać techniki programistyczne,
- Ulepszać C++ tak, aby ułatwić projektowanie systemów i bibliotek zamiast wprowadzać nowe możliwości, które mogłyby być przydatne tylko w szczególnych zastosowaniach,
- Zwiększać bezpieczeństwo typów poprzez wprowadzenie bezpieczniejszych zamienników aktualnych, mniej bezpiecznych technik,
- Zwiększać wydajność i zdolność bezpośredniej współpracy ze sprzętem,
- Dostarczyć odpowiednich rozwiązań dla rzeczywistych problemów praktycznych,
- Stosować zasadę "zero narzutu" (narzędzie może wymagać dodatkowego wsparcia tylko wtedy, gdy jest użyte),
- Uczynić C++ łatwiejszym do nauczania i uczenia się bez usuwania narzędzi potrzebnych ekspertom.
Rozszerzenia w rdzeniu C++
Obszary rdzenia C++, które zostały znacznie ulepszone, to między innymi: obsługa wielowątkowości, umożliwienie programowania generycznego, jednolite inicjowanie i rozszerzenia wydajnościowe. W niniejszym artykule główne cechy rdzenia C++ i zmiany w nim są pogrupowane w trzech sekcjach: rozszerzenia wydajnościowe, rozszerzenia związane z ułatwieniem stosowania C++ oraz nowe możliwości. Część cech mogłoby pasować do kilku grup naraz, ale zostały zaszeregowane tak, aby uwypuklić ich główne przeznaczenie.
Rozszerzenia wydajnościowe w rdzeniu języka
Dotyczą cech języka poprawiających wydajność. Chodzi o szybkość lub zajmowaną pamięć.
Referencja do r-wartości/Semantyka przeniesienia
- Źródło[5]
W C++ obiekty tymczasowe (określane jako r-wartości, czyli wartości będące po prawej stronie operatora przypisania '='), mogą być przekazywane do funkcji, ale tylko jako referencje do stałej (const &). Z punktu widzenia takiej funkcji nie jest możliwe rozróżnienie pomiędzy aktualną r-wartością a zwykłym obiektem przekazanym jako const &. Ponieważ jest to typ const & nie jest także możliwa zmiana jego wartości.
C++11 wprowadza nowy typ referencyjny, zwany referencją do r-wartości, definiowany jako typename &&. Może być akceptowany jako nie-stała wartość, co pozwala obiektom na ich modyfikację. Taka zmiana umożliwia pewnym obiektom na stworzenie semantyki przenoszenia.
Przykładowo, std::vector jest wewnętrznie adapterem na zwykłą tablicę w stylu języka C, wraz z wielkością tej tablicy. Jeśli obiekt tymczasowy typu vector jest tworzony lub zwracany przez funkcję, to może być przechowany tylko przez stworzenie nowego obiektu vector wraz z kopiami wszystkich r-wartości. Jeśli obiekt tymczasowy jest niszczony, niszczone są też wszystkie dane. Jeśli używamy referencji do r-wartości, to "konstruktor przenoszący" (nazwa analogiczna do konstruktora kopiującego w C++) std::vector może za pomocą referencji do r-wartości po prostu kopiować wskaźnik do tablicy z r-wartości, zostawiając r-wartość w stanie pustym. Unikamy wtedy kopiowania całej tablicy, a niszczenie pustego obiektu tymczasowego odbywa się bez dealokacji pamięci. Funkcja zwracająca obiekt tymczasowy typu vector musi zwrócić tylko typ std::vector<>&&. Jeśli obiekt vector posiada konstruktor przenoszący, wtedy ten konstruktor może być wywołany, co zapobiega wielu nadmiarowym operacjom alokacji pamięci.
Niech na potrzeby niniejszego artykułu referencja do r-wartości nazywa się r-referencją. Analogicznie, niech zwykła referencja nazywa się l-referencją. Wtedy mając l-referencje i r-referencje, programista może pisać dokładne funkcje przekazujące (ang. forwarding functions). Dokładność polega tu na całkowitej zgodności typów. Gdy ma do dyspozycji tylko l-referencje (tak jest w starszym standardzie C++), wtedy ogólnie rzecz biorąc, takie funkcje miałyby tylko przybliżone listy argumentów (nie do końca zgodne typy). Gdy l-referencje i r-referencje stosujemy łącznie z funkcjami lub metodami szablonowymi o zmiennych listach argumentów (ang. variadic templates), wtedy uzyskuje się możliwość dokładnego przekazywania argumentów do innej funkcji o ustalonej liście parametrów. Jest to najbardziej przydatne przy przekazywaniu parametrów konstruktora w celu stworzenia takich funkcji fabrykujących (ang. factory functions), które automatycznie wywołają właściwy konstruktor dla tych argumentów.
Uogólnione wyrażenia stałe
- Źródło[6]
W C++ stałe wyrażenia to wyrażenia takie jak 3+4, które zawsze zwracają ten sam wynik i nie wywołują żadnych dodatkowych efektów ubocznych (ang. side effect). Stałe wyrażenia są dla kompilatorów okazją do optymalizacji, ponieważ kompilatory często wykonują te wyrażenia w czasie kompilacji i wstawiają ich wyniki do programu. Jest wiele miejsc, gdzie specyfikacja C++ wymaga użycia stałych wyrażeń. Są to między innymi definicja tablicy i wartości typów wyliczeniowych. Jednak jeśli wyrażenie zawiera wywołanie funkcji lub wykonanie konstruktora obiektu nie będzie zinterpretowane przez kompilator jako stałe. Na przykład:
int GetFive() {return 5;}
int someValues[GetFive() + 5]; //stwórz tablicę 10 elementów typu ''int''. Jest to błąd w C++.
Zostaje wykryty błąd, ponieważ GetFive() + 5 nie jest wyrażeniem stałym. Kompilator nie potrafi rozpoznać, że GetFive jest stałe w czasie uruchamiania, ponieważ w teorii ta funkcja mogłaby wpływać na globalną zmienną, wywołać funkcje nie będące stałe w czasie uruchamiania, itd.
C++11 wprowadza nowe słowo kluczowe constexpr, które pozwala użytkownikowi na zagwarantowanie, że funkcja lub konstruktor obiektu są stałymi podczas kompilacji. W C++11 powyższy kod można przepisać następująco:
constexpr int GetFive() {return 5;}
int someValues[GetFive() + 5]; //stwórz tablicę 10 elementów typu ''int''. Poprawne w C++11
To pozwala kompilatorowi rozpoznać i zweryfikować, że GetFive jest stałą podczas kompilacji.
Zastosowanie constexpr do funkcji narzuca bardzo ścisłe ograniczenia na to, co funkcja może robić:
- Funkcja musi posiadać typ zwracany różny od void,
- Zawartość funkcji musi być w postaci "return wyrażenie",
- wyrażenie musi być stałym wyrażeniem po zastąpieniu argumentu. To stałe wyrażenie może albo wywołać inne funkcje tylko wtedy, gdy te funkcje też są zadeklarowane ze słowem kluczowym constexpr albo używać inne stałe wyrażenia,
- Wszystkie formy rekursji w stałych wyrażeniach są zabronione,
- Funkcja zadeklarowana ze słowem kluczowym constexpr nie może być wywoływana, dopóki nie będzie zdefiniowana w swojej jednostce translacyjnej.
Zmienne także mogą być zdefiniowane jako stałowyrażeniowe:
constexpr double silaGrawitacji = 9.8;
constexpr double grawitacjaKsiezyca = silaGrawitacji / 6;
Zmienne typu constexpr (stałowyrażeniowe) są niejawnie typu const. Mogą one przechować wyniki wyrażeń stałych lub stałowyrażeniowych konstruktorów (czyli zdefiniowanych ze słowem kluczowym constexpr).
W celu konstrukcji wartości stałowyrażeniowych z typów danych zdefiniowanych przez użytkownika, konstruktory muszą także być zadeklarowane jako constexpr. Taki stałowyrażeniowy konstruktor musi być zdefiniowany przed użyciem w jednostce translacyjnej; podobnie jest z metodami stałowyrażeniowymi. Muszą mieć puste ciała funkcji i muszą inicjować swoje składowe za pomocą stałych wyrażeń. Destruktory takich typów powinny być trywialne.
Typy użytkownika, gdy kopiują constexpr, także powinny być zdefiniowane jako constexpr w celu umożliwienia zwracania ich przez wartość przez stałowyrażeniową funkcję. Dowolna metoda klasy, np. konstruktor kopiujący, operatory przeciążone, itd., mogą być zadeklarowane jako constexpr dopóki odpowiadają one definicji stałowyrażeniowej metody. Umożliwia to kompilatorowi kopiowanie klas w czasie kompilacji, wykonywanie operacji na nich, itd.
Stałowyrażeniowa funkcja lub konstruktor, mogą być wywołane z niestałowyrażeniowymi argumentami. Jeśli stałowyrażeniowy literał całkowity może być przypisany do niestałowyrażeniowej zmiennej, to stałowyrażeniowa funkcja może być także wywołana z niestałowyrażeniowymi parametrami, a wynik jest przechowywany w zmiennej niestałowyrażeniowej. Słowo kluczowe constexpr tylko umożliwia stałość podczas kompilacji tylko wtedy, gdy wszystkie składowe wyrażenia są typu constexpr.
W standardzie C++20 do języka wprowadzono nowe słowo kluczowe – consteval. Funkcja oznaczona jako consteval, nazywana funkcją natychmiastową, zachowuje się tak samo jak funkcja constexpr, jednak może być wywoływana jedynie z argumentami constexpr. Wartość zwracana przez funkcję natychmiastową musi być zawsze znana podczas kompilacji[7].
Modyfikacja definicji tradycyjnych struktur danych (TSD)
W starszym C++, struktura musi spełniać kilka wymagań, by stać się tradycyjną strukturą danych (TSD) (ang. Plain Old Data, POD). Jest kilka powodów, by chcieć, aby większa liczba typów danych spełniała te wymagania. Typy spełniające te wymagania pozwalają implementacjom na takie ułożenie składowych obiektów, które byłyby kompatybilne z C. Jednak lista reguł w C++03 jest przesadnie restrykcyjna.
C++11 rozluźnia kilka reguł dotyczących tworzenia obiektów TSD.
Klasa/struktura jest uważana za TSD, jeśli jest trywialna, standardowo ułożona i nie posiada żadnych niestatycznych składowych niebędących TSD-ami. Klasa/struktura jest trywialna, jeśli:
- Posiada trywialny konstruktor domyślny. Może używać składni dla konstruktora domyślnego (JakisKonstruktor() = default;),
- Posiada trywialny konstruktor kopiujący, być może ze składnią domyślności, tj. ze słowem kluczowym default,
- Posiada trywialny operator przypisania, być może ze składnią domyślności,
- Posiada trywialny destruktor, który nie może być wirtualny.
Standardowo ułożona klasa/struktura to taka, która:
- Posiada tylko niestatyczne pola, które są standardowo ułożone,
- Posiada ten sam poziom dostępu (private, protected, public) dla wszystkich niestatycznych składowych,
- Nie posiada wirtualnych metod,
- Nie posiada wirtualnych klas bazowych,
- Posiada tylko standardowo ułożone klasy bazowe,
- Nie posiada klas bazowych takiego samego typu jak pierwsze niestatyczne pole,
- Albo nie posiada klas bazowych z niestatycznymi składowymi, albo nie posiada niestatycznych pól w najbardziej pochodnej klasie i posiada co najwyżej jedną klasę bazową z niestatycznymi składowymi. W zasadzie, może być tylko jedna klasa z niestatycznymi składowymi w hierarchii klas.
Rozszerzenia w rdzeniu języka związane ze zwiększeniem wydajności kompilacji i łączenia
Szablony zewnętrzne
C++ musi stworzyć instancję szablonu zawsze kiedy napotka w pełni określony szablon w jednostce translacyjnej. Może to spowodować dramatyczny wzrost czasu kompilacji, szczególnie gdy instancja szablonu jest tworzona w wielu jednostkach translacyjnych przy użyciu tych samych parametrów. W starszym C++ nie jest bowiem możliwe wstrzymanie tworzenia instancji szablonu w takiej sytuacji.
C++11 wprowadza ideę szablonów zewnętrznych. Starszy C++ już posiada możliwość zmuszania kompilatora do tworzenia instancji w określonym miejscu.
template class std::vector<MojaKlasa>;
W celu zablokowania tworzenia instancji w jednostce translacyjnej, w C++11 wystarczy przepisać powyższy kod jako:
extern template class std::vector<MojaKlasa>;
W ten sposób powstrzymamy kompilator od tworzenia instancji szablonu w danej jednostce translacyjnej.
Rozszerzenia rdzenia związane z ułatwieniem stosowania C++
Rozszerzenia te istnieją w celu ułatwienia pisania programów C++. Mogą one poprawiać bezpieczeństwo typów, minimalizować powtórzenia kodu, uczynić trudniejszym napisanie kodu podatnego na błędy, itd.
Listy inicjujące
- Źródło[8]
Pomysł inicjowania list jest w C++ zapożyczony z C. Ideą jest, by struktura lub tablica były tworzone, podając po prostu listę argumentów o kolejności zgodnej, odpowiednio, z kolejnością definicji składowych struktury lub kolejnymi elementami tablicy. Te listy inicjujące są rekursywne i mogą być zastosowane także do tablicy struktur albo struktury zawierającej inną strukturę. C++ posiada konstruktory, które naśladują takie inicjowanie obiektu, ale nie mają takich możliwości jak listy inicjujące. Starszy C++ pozwala stosować listy inicjujące do struktur lub klas. Jednak w przypadku klas, muszą one spełniać wymagania jak dla TSD, aby dało się inicjować je w taki sposób. Jeśli klasa nie spełnia zasad jak dla TSD (np. std::vector lub boost::array), wtedy te listy na nich nie działają.
C++11 wiąże koncepcję inicjowania list z typem zwanym std::initializer_list. To pozwoli konstruktorowi lub metodom na podanie takich list jako argumentów. Na przykład:
class JakasKlasa
{
public:
JakasKlasa(std::initializer_list<int> list);
};
To pozwala obiektowi typu JakasKlasa być konstruowanym z sekwencji liczb całkowitych, tak jak poniżej:
JakasKlasa jakasZmienna = {1, 4, 5, 6};
Ten konstruktor jest specjalnym rodzajem konstruktora, zwanym konstruktorem list inicjujących. Klasy z takim konstruktorem są traktowane specjalnie podczas jednolitego inicjowania.
Listy inicjujące w C++11 mogą być początkowo inicjowane tylko statycznie przez kompilator C++11 przy użyciu składni {}. Lista może być kopiowana raz przy konstrukcji i jest to tylko kopia przez referencję. Lista inicjująca jest stałą; ani jej składowe ani też dane w tych składowych nie mogą być zmienione po jej utworzeniu.
Ponieważ std::initializer_list<> jest prawdziwym typem, więc może być używana w innych miejscach poza konstruktorem klasy. Zwykłe funkcje mogą przyjmować ustalone listy inicjujące jako argumenty. Przykładowo:
void NazwaFunkcji(std::initializer_list<float> lista);
NazwaFunkcji({1.0f, -3.45f, -0.4f});
Jednolite inicjowanie
W starszym C++ jest wiele problemów z inicjowaniem typów. Jest kilka sposobów inicjowania typów i nie są one zamienne, jeśli chodzi o wyniki działania. Na przykład tradycyjna składnia konstruktora może wyglądać jak deklaracja funkcji i muszą być przedsięwzięte kroki zabezpieczające przed pomyłką kompilatora. Tylko typy TSD mogą być inicjowane grupowo (przy użyciu składni JakisTyp zm = {/*inicjowanie*/};
).
C++11 posiada składnię w pełni ujednolicającą inicjowanie dowolnych typów, która jest rozszerzeniem składni listy inicjującej:
struct PodstStrukt
{
int x;
float y;
};
struct AlternatStrukt
{
AlternatStrukt(int _x, float _y) : x(_x), y(_y) {}
private:
int x;
float y;
};
PodstStrukt zm1{5, 3.2f};
AlternatStrukt zm2{2, 4.3f};
Zainicjowanie zm1 działa dokładnie tak, jakby to była lista inicjująca z C. Każda publiczna zmienna będzie inicjowana przez każdą odpowiadającą wartość z listy inicjującej. Niejawne konwersje typów będą przeprowadzane w razie potrzeby i jeśli nie jest dostępna konwersja typów, wtedy kompilator zgłosi błąd kompilacji.
Zainicjowanie zm2 po prostu wywołuje konstruktor.
Konstrukcja z użyciem jednolitego inicjowania może usunąć potrzebę określenia niektórych typów:
struct IdString
{
std::string nazwa;
int identyfikator;
};
IdString zm3{"JakasNazwa", 4};
Taka składnia automatycznie zainicjuje typ std::string argumentem typu const char *. Możliwe jest jeszcze:
IdString GetString()
{
return {"JakasNazwa", 4}; //Brak wyraźnego określenia typu zwracanej wartości
}
Jednolite inicjowanie nie zastąpi składni konstruktora. Ciągle mogą się zdarzyć sytuacje, w których składnia konstruktora będzie wymagana. Jeśli klasa posiada konstruktor list inicjujących (NazwaTypu(initializer_list<JakisTyp>);), wtedy taki konstruktor posiada pierwszeństwo przed innymi formami inicjowania jeśli lista inicjująca odpowiada typowi sekwencji konstruktora. Wersja std::vector w C++11 będzie posiadać konstruktor list inicjujących dla swojego typu szablonu:
std::vector<int> wekt{4};
Instrukcja wywoła konstruktor list inicjujących, a nie konstruktor std::vector, który pobiera pojedynczy parametr rozmiaru i tworzy wektor o takim rozmiarze. By wywołać ten konstruktor, użytkownik musi użyć standardowej składni konstruktora.
Automatyczne określenie typu
W C++ (i w C), typ zmiennej musi być podany, by móc używać tej zmiennej. Jednakże od momentu pojawienia się typów szablonowych i technik metaprogramowania, typ czegokolwiek, w szczególności typ wartości zwracanej przez funkcję, nie jest łatwy do wyrażenia. Przechowywanie wartości pośrednich w zmiennych jest trudne i często wymaga wiedzy o wewnętrznych mechanizmach używanej biblioteki do metaprogramowania.
C++11 pozwala na uproszczenie tego problemu na dwa sposoby. Po pierwsze, w definicji zmiennej z jawnym inicjowaniem można użyć słowa kluczowego auto. Można w ten sposób utworzyć zmienną o typie takim, jak typ inicjującej wartości:
auto trudnyDoOkresleniaTypZmiennej = std::bind(&JakasFunkcja, _2, _1, jakisObiekt);
auto innaZmienna = L"To jest tekst";
Typem trudnyDoOkresleniaTypZmiennej może być cokolwiek zwracanego przez pewną funkcję szablonową pod boost::bind
dla danych argumentów. Typ ten jest łatwy do określenia przez kompilator, natomiast dla użytkownika jest to trudne.
Typ innaZmienna jest także dobrze zdefiniowany, ale tym razem jest to łatwiejsze dla użytkownika. Jest to typ const wchar_t *, który jest taki sam jak dla literału tekstowego.
Dodatkowo, słowo kluczowe decltype może być zastosowane w celu określenia typu w czasie kompilacji. Przykładowo:
int jakisInt;
decltype(jakisInt) innaZmiennaInt = 5;
Jest to bardziej przydatne w połączeniu ze słowem kluczowym auto, ponieważ typ zmiennej auto jest znany tylko kompilatorowi. Jednak decltype może być także bardzo przydatny w takich wyrażeniach w kodzie, które intensywnie używają przeciążonych operatorów i typów specjalizowanych.
auto
jest także przydatne przy ograniczaniu rozwlekłości kodu. Na przykład, zamiast pisać
for (vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)
programista może użyć krótszego:
for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)
Różnica wzrasta, gdy programista zagnieżdża kontenery, ale w takich wypadkach można równie dobrze używać znane typedef
y do ograniczania ilości kodu.
Pętla for oparta na zakresie
- Źródło[9]
Biblioteka Boost definiuje kilka koncepcji zakresu. Zakresy reprezentują kontrolowaną listę pomiędzy dwoma jej punktami. Kontenery uporządkowane są nad zbiorem koncepcji zakresu i dwa iteratory w kontenerze uporządkowanym także definiują zakres. Zakresy i algorytmy operujące na zakresach będą włączone do biblioteki standardowej C++11. Dodatkowo C++11 oferuje wsparcie językowe dla nich.
Poniższa pętla for jest nowym typem for, stworzonej do łatwej iteracji po zakresie:
int moja_tablica[5] = {1, 2, 3, 4, 5};
for(int &x : moja_tablica)
{
x *= 2;
}
Pierwsza sekcja nowego for (przed dwukropkiem) definiuje zmienną, która będzie użyta do iterowania po zakresie. Zmienna ta, tak jak zmienne w zwykłej pętli for, ma zasięg ograniczony do zasięgu pętli. Druga sekcja (po dwukropku), reprezentuje iterowany zakres. W tym przypadku, zwykła tablica jest konwertowana do zakresu. Mógłby to być na przykład std::vector albo inny obiekt spełniający koncepcję zakresu.
Funkcje i wyrażenia lambda
- Źródło[9]
W starszym C++ użytkownik często chciałby zdefiniować predykatowe funkcje w pobliżu wywołań takich funkcji, jak na przykład pochodzących ze standardowej biblioteki algorithm (szczególnie sort i find). Język zapewnia tylko jeden mechanizm do takiej operacji: możliwość zdefiniowania klasy wewnątrz funkcji. Jest to często niewygodne i rozwlekłe oraz zaburza przepływ kodu. W dodatku, reguły C++ dla klas zdefiniowanych w funkcjach nie pozwalają im być w wersjach szablonowych, więc ich używanie jest często niemożliwe.
Oczywistym rozwiązaniem mogłoby być zezwolenie na definicję wyrażeń lambda i funkcji lambda. Tak właśnie jest w C++11, w którym można definiować funkcje lambda.
Przykładowa funkcja lambda może być zdefiniowana następująco:
[](int x, int y) { return x + y; }
Ta nienazwana funkcja zwraca typ decltype(x+y). Określenie zwracanego typu może być pominięte tylko, gdy wyrażenie lambda jest w formie return wyrażenie, czyli stanowi pojedynczą instrukcję.
Poniżej podano bardziej rozbudowany przykład wraz z określaniem zwracanego typu.
[](int x, int y) -> int {int z = x + y; return z + x;}
W tym przykładzie zmienna tymczasowa z jest tworzona w celu przechowania wyniku pośredniego. W zwykłych funkcjach, taka wartość pośrednia nie jest przechowywana pomiędzy wywołaniami. Określenie zwracanego typu może być całkowicie pominięte, gdy funkcja lambda nie zwraca żadnej wartości (tj. gdy zwraca typ void).
Referencje do zmiennych zdefiniowanych w zasięgu funkcji lambda też mogą być podobnie używane. Zbiór zmiennych tego typu jest najczęściej nazywany domknięciem. Domknięcia są definiowane i używane następująco:
std::vector<int> jakasLista;
int suma = 0;
std::for_each(jakasLista.begin(), jakasLista.end(), [&suma](int x) { suma += x; });
std::cout << suma;
Kod wyświetla sumę wszystkich elementów listy. Zmienna suma jest przechowywana jako domknięcie funkcji lambda. Ponieważ jest referencją do zmiennej stosu suma, więc można zmienić jej wartość.
Zmienne domknięcia zmiennych stosu mogą być też definiowane bez symbolu referencji &. Wtedy będzie to znaczyć, że funkcja lambda będzie kopiować wartości. Użytkownik jest więc zmuszony do deklaracji albo tworzenia referencji zmiennych stosu albo ich kopiowania. Tworzenie referencji zmiennych stosu jest niebezpieczne. Jeśli obiekt domykający, który zawiera referencje do zmiennych stosu, jest wywoływany po bloku zasięgu swojego utworzenia, to zachowanie końcowe będzie niezdefiniowane.
W funkcjach lambda, co do których jest gwarantowane ich uruchomienie w zasięgu swoich definicji, jest możliwe używanie wszystkich dostępnych zmiennych stosu bez posiadania jawnych referencji do tych zmiennych:
std::vector<int> jakasLista;
int suma = 0;
std::for_each(jakasLista.begin(), jakasLista.end(), [&](int x) { suma += x; });
Poszczególne implementacje mogą się różnić, ale oczekuje się, że funkcja lambda przechowa aktualny stosowy wskaźnik do funkcji tworzonej wewnątrz, zamiast poszczególnych referencji do zmiennych stosowych.
Jeśli zamiast [&] jest używany [=], wtedy wszystkie referencjowane wartości będą kopiowane, pozwalając funkcji lambda na jej używanie po skończonych czasach życia początkowych zmiennych.
Można określić domyślne zachowywanie się funkcji lambda jeśli chodzi o kwestię referencji. Gdy użytkownik chce np. aby operacje na wszystkich zmiennych były dokonywane poprzez referencje do nich, a tylko jedna zmienna była kopiowana, to może zrobić to następująco:
int suma = 0;
int wartosc = 5;
[&, wartosc](int x) { suma += (x * wartosc) };
Wtedy zmienna suma będzie przechowywana jako referencja, a zmienna wartosc - jako kopia.
Jeśli funkcja lambda jest definiowana przez metodę klasy, wtedy staje się funkcją zaprzyjaźnioną tej klasy. Taka funkcja lambda może używać referencji do obiektów tej klasy i mieć dostęp do jej wewnętrznych składowych:
[](JakisTyp *wskTypu) (wskTypu->JakasPrywatnaMetoda());
Będzie to działać tylko wtedy, gdy zasięg tworzenia tej funkcji lambda będzie wewnątrz metody JakisTyp.
Obsługa wskaźnika this, wskazującego na obiekt obsługiwany przez daną metodę, jest specjalna. Musi być wyraźnie zaznaczona w funkcji lambda:
[this]() {this->JakasPrywatnaMetoda()};
Zastosowanie formy [&] lub [=] funkcji lambda automatycznie udostępni this.
Funkcje lambda są obiektami funkcyjnymi o typie zależnym od implementacji. Nazwa takiego typu jest dostępna tylko kompilatorowi. Jeśli więc użytkownik zechce używać funkcji lambda jako parametru, to musi albo użyć parametru o typie szablonowym albo użyć std::function do przekazywania wartości lambda. Używanie słowa kluczowego auto może lokalnie przechować funkcję lambda:
auto mojaFunkcjaLambda = [this]() { this->JakasPrywatnaMetoda() };
Jednak gdy funkcja lambda przekazuje wszystkie zmienne domknięcia przez referencje lub nie posiada zmiennych domknięcia, wtedy jego typ będzie publicznie dziedziczony z std::reference_closure<R(P)>, gdzie R(P) jest sygnaturą funkcji ze zwracaną wartością. Przewiduje się, że będzie to efektywniejsza reprezentacja funkcji lambda niż przekazywanie jej przez std::function:
std::reference_closure<void()> mojaFunkcjaLambda = [this]() { this->JakasPrywatnaMetoda() };
mojaFunkcjaLambda();
Nowa składnia deklaracji i definicji funkcji
Składnia deklaracji funkcji w standardzie C była dokładnie dopasowana do zestawu cech języka C. Gdy C++ wyewoluował z C, to zachowywał podstawową składnię, rozszerzając ją tylko wtedy, kiedy było to konieczne. Jednakże C++ stawał się coraz bardziej skomplikowany i liczba wyjątków od podstawowej składni wzrastała (dotyczy to szczególnie deklaracji szablonów funkcji). Poniższy przykład jest błędny w C++03:
template< typename LHS, typename RHS>
Ret // NIEPRAWIDŁOWE!
AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;}
Typem Ret jest cokolwiek, co jest wynikiem sumowania zmiennych typów LHS i RHS. Nawet później opisywana funkcjonalność decltype nie pomaga w tej sytuacji:
template< typename LHS, typename RHS>
decltype(lhs + rhs) // NIEPRAWIDŁOWE!
AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;}
Jest to nielegalne w C++ ponieważ lhs i rhs nie są jeszcze zdefiniowane; nie będą one prawidłowymi identyfikatorami, dopóki analizator składniowy nie sparsuje reszty prototypu funkcji.
By to obejść, C++11 wprowadza nową składnię deklaracji i definicji funkcji:
template< typename LHS, typename RHS>
auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs + rhs) {return lhs + rhs;}
Taka składnia może być użyta do bardziej skomplikowanych deklaracji i definicji:
struct SomeStruct
{
auto FuncName(int x, int y) -> int;
};
auto SomeStruct::FuncName(int x, int y) -> int
{
return x + y;
}
Usprawnienie konstruowania obiektów
- Źródło[5]
W C++03 konstruktory nie mogą wywoływać innych konstruktorów; każdy konstruktor musi sam konstruować wszystkie składowe klasy lub wywołać metody tej samej klasy. Konstruktory klasy bazowej nie mogą być bezpośrednio udostępnione klasom pochodnym; każda klasa pochodna musi implementować konstruktory nawet wtedy, gdy klasa bazowa byłaby wystarczająca. Niestałe pola klasy nie mogą być inicjowane podczas deklaracji klasy. Mogą one być inicjowane tylko w konstruktorze.
C++11 wprowadza możliwość obejścia wszystkich powyższych problemów.
C++11 pozwala na wywołanie innych równorzędnych konstruktorów (znanych jako delegaci). To pozwala na wykorzystanie cech innego konstruktora za pomocą niewielkiego dodatku kodu. Tak jest na przykład w języku Java.
Składnia jest następująca:
class SomeType
{
int number;
public:
SomeType(int newNumber) : number(newNumber) {}
SomeType() : SomeType(42) {}
};
Ważne jest, aby pamiętać o różnicach pomiędzy C++03 a C++11 w tym zakresie: C++03 uważa, że obiekt jest skonstruowany, gdy jego konstruktor zakończy działanie. Natomiast w C++11 obiekt jest skonstruowany, jeśli dowolny konstruktor zakończy swe działanie. Jeśli wielokrotne wykonywanie konstruktorów jest dozwolone, to znaczy, że każdy konstruktor delegatowy będzie wykonywany na już skonstruowanym tym samym obiekcie. Konstruktory klas pochodnych będą wywołane wtedy, gdy wszystkie konstruktory delegatowe ich klas bazowych będą zakończone.
Dla konstruktorów klas bazowych C++11 zezwoli na określenie, że konstruktory klasy podstawowej będą dziedziczone. To znaczy, że kompilator C++11 wygeneruje kod wykonujący dziedziczenie i przekierowanie z klasy pochodnej do bazowej. Warto zauważyć, że jest to operacja typu wszystko albo nic; albo wszystkie konstruktory klasy bazowej są przekierowywane albo żaden. A także, że istnieją ograniczenia na wielokrotne dziedziczenie: konstruktory klas nie mogą być dziedziczone z dwóch klas używających konstruktorów o tej samej sygnaturze, oraz nie mogą istnieć konstruktory w klasie bazowej i pochodnej o tej samej sygnaturze.
Składnia jest następująca:
class BaseClass
{
public:
BaseClass(int iValue);
};
class DerivedClass : public BaseClass
{
public:
using BaseClass::BaseClass;
};
Dla inicjowania składowych C++11 dopuszcza poniższą składnię:
class SomeClass
{
private:
int iValue = 5;
};
Dowolny konstruktor klasy zainicjuje iValue wartością 5, jeśli konstruktor nie nadpisze tej zmiennej własną wartością:
class SomeClass
{
public:
SomeClass() {}
explicit SomeClass(int iNewValue) : iValue(iNewValue) {}
private:
int iValue = 5;
};
Pusty konstruktor zainicjuje iValue tak, jak jest to w definicji klasy, ale konstruktor pobierający argument zainicjuje to wartością swojego argumentu.
Wskaźnik typu nullptr
W starszym C++, stała 0 spełnia dwie funkcje: stałej całkowitej i pustego wskaźnika (jest to cecha z języka C od roku 1972).
Przez lata programiści obchodzili tę niejednoznaczność za pomocą identyfikatora NULL
zamiast 0
. Jednak w C++ dwie decyzje projektowe sprawiły, że pojawiła się jeszcze jedna niejednoznaczność. W C NULL
jest makrem preprocesora zdefiniowanym jako ((void*)0)
lub 0
. W C++ niejawna konwersja z void*
do wskaźnika innego typu jest niedozwolona, więc nawet takie proste przypisanie jak char* c = NULL
mogłoby być w tym przypadku błędem kompilacji. W celu usunięcia tego problemu standard C++ zapewnia, że NULL
będzie rozwinięte do 0
, które jest specjalnym przypadkiem i jest więc dozwolona konwersja do dowolnego typu wskaźnikowego. Jednak sytuacja komplikuje się w przypadku przeciążania. Przykładowo, niech program posiada deklarację:
void foo(char *);
void foo(int);
i programista wywoła wtedy foo(NULL)
.
To zaś wywoła wersję foo(int)
, która prawie na pewno nie była zamierzona przez programistę.
nullptr
nie może być przypisane do typów całkowitych, ani porównywane z nimi; może być porównywane z dowolnymi typami wskaźnikowymi.
Obecna rola stałej 0 będzie zachowana dla kompatybilności ze starszymi wersjami C++. Jeśli nowa składnia będzie powszechnie zaakceptowana, wtedy używanie 0
i NULL
jako pustych wskaźników będzie przez Komitet C++ odradzane (ang. deprecated).
Silnie typowane wyliczenia
- Źródło[9]
W starszym C++ typy wyliczeniowe nie są bezpieczne typowo (ang. type-safe). Są one de facto typami całkowitymi nawet, gdy te typy są oddzielne. To pozwala na porównywanie pomiędzy dwoma wartościami różnych wyliczeń. Jedynym przejawem ich bezpieczeństwa typowego zapewnionego przez standard C++03 jest to, że typ całkowity lub wartość jakiegoś typu wyliczeniowego nie konwertuje się niejawnie na inny typ wyliczeniowy. Dodatkowo, typ całkowity będący u podstawy typu wyliczeniowego oraz jego rozmiar nie mogą być jawnie określone; jest to zależne od implementacji. Na koniec, zasięg typu wyliczeniowego jest ograniczony do zamykającego zasięgu. Skoro tak, to nie jest możliwy konflikt nazw elementów wyliczeń dwóch różnych typów wyliczeniowych.
C++11 zezwala na taką specjalną klasyfikację typów wyliczeniowych, która uniknie powyższych problemów. Jest wyrażany za pomocą deklaracji enum class:
enum class Enumeration
{
Val1,
Val2,
Val3 = 100,
Val4 /* = 101 */,
};
Takie wyliczenie jest bezpieczne typowo. Wartości klasy enum nie są niejawnie konwertowane na typy całkowite i nie mogą zatem być porównywane z typami całkowitymi. Przykładowo:
Enumeration::Val4 == 101
spowoduje błąd kompilacji.
Typ podstawowy klasy enum jest jawnie określany. Domyślnie, jak to jest pokazane wyżej, jest to typ int, ale może być zmieniony następująco:
enum class Enum2 : unsigned int {Val1, Val2};
Zasięg wyliczenia jest także definiowany jako zasięg nazwy wyliczenia. Używanie nazwy wyliczenia wymaga jawnego podania zasięgu. Val1 jest niezdefiniowany, ale Enum2::Val1 już jest zdefiniowane.
Dodatkowo C++11 zezwala standardowym wyliczeniom na podawanie jawnych zasięgów jednocześnie z definicją typu podstawowego:
enum Enum3 : unsigned long {Val1 = 1, Val2};
Nazwy wyliczenia są zdefiniowane w zasięgu wyliczenia (Enum3::Val1), ale dla wstecznej kompatybilności, nazwy wyliczenia są dostępne także w zamykającym zasięgu.
Usunięcie problemu trójkątnego nawiasu
Wraz z wprowadzeniem programowania generycznego za pomocą szablonów, stało się konieczne wprowadzenie nowych nawiasów. Poza nawiasami okrągłymi, kwadratowymi i klamrowymi, C++ wprowadziło nawiasy trójkątne. Przy okazji pojawiła się leksykalna niejednoznaczność, która często była w starszym C++ rozwiązywana nieprawidłowo z punktu widzenia programisty i prowadziła do błędu parsowania:
typedef std::vector<std::vector<int> > Table ; // Ok.
typedef std::vector<std::vector<bool>> Flags ; // Błąd! ">>" interpretowane jako przesunięcie bitowe na prawo
void func( List<B>= default_val ) ; // Błąd! ">=" interpretowane jako porównanie
void func( List<List<B>>= default_val ) ; // Błąd! ">>=" interpretowane jako operator przypisania przesuwający bitowo na prawo
template< bool I > class X {};
X<(1>2)> x1 ; // Ok.
X< 1>2 > x1 ; // Błąd! Pierwszy ">" interpretowany jako zamykający nawias trójkątny
W C++11 w fazie leksykalnej analizy znak ">" będzie interpretowany jako zamykający nawias trójkątny nawet wtedy, gdy jest natychmiast następowany przez ">" lub "=", jeśli najbardziej wewnętrznie zagnieżdżony otwarty nawias jest trójkątny. To naprawia wszystkie powyższe błędy z wyjątkiem ostatniego, dla którego programista wciąż musi wstawiać ujednoznaczniające nawiasy.
X<(1>2)> x1 ; // Ok.
W ten sposób, po lewym nawiasie okrągłym i aż do prawego nawiasu okrągłego, kompilator nie rozpozna znaków <> jako nawiasów trójkątnych.
Jawne operatory przekształcenia
Standard C++ wprowadził słowo kluczowe explicit jako modyfikator konstruktorów zabraniający jednoargumentowym konstruktorom bycie używanym jako niejawne operatory przekształcenia. Jednak to nie działa w przypadku aktualnych operatorów przekształcenia. Na przykład, klasa inteligentnego wskaźnika może mieć zdefiniowane operator bool() w celu podobnego zachowywania się jak zwykłe wskaźniki (ang. primitive pointers); jeśli zawiera takie przekształcenie, może być testowany na if(smart_ptr_variable) (które byłoby prawdziwe gdyby wskaźnik był niepusty i fałszywe w przeciwnym razie). Jednak taka konstrukcja pozwala także na inne, niezamierzone przekształcenia. Ponieważ bool w C++ jest zdefiniowany jako typ arytmetyczny, może być niejawnie przekształcony na typ całkowitoliczbowy lub nawet na typy zmiennoprzecinkowe; które z kolei pozwalają na matematyczne operacje, co nie jest zamierzone przez użytkownika.
W C++11 słowo kluczowe explicit może być zastosowane także do operatorów przekształcenia. Tak jak konstruktory, zapobiega dalszym, niejawnym przekształceniom.
Aliasy szablonów
W starszym C++ możliwe jest definiowanie aliasów szablonów tylko wtedy, gdy wszystkie parametry szablonów są zdefiniowane. Nie jest możliwe tworzenie aliasów z niezdefiniowanymi parametrami szablonowymi. Na przykład:
template< typename first, typename second, int third> class SomeType;
template< typename second> typedef SomeType<OtherType, second, 5> TypedefName; //Nieprawidłowe w C++
Ten kod się nie skompiluje.
C++11 dodaje taką możliwość. Składnia jest ciągle w fazie rozwoju, ale ostatnia wersja wygląda tak:
template< typename first, typename second, int third> class SomeType;
template< typename second> using TypedefName = SomeType<OtherType, second, 5>;
Transparentny odśmiecacz
C++11 nie posiada wbudowanego transparentnego odśmiecacza, ale obecne są w nim cechy, które ułatwią implementację takiego odśmiecacza.
Kwestia pełnej obsługi odśmiecacza w C++11 jest odłożona do późniejszych wersji Standardu albo Raportu Technicznego (ang. Technical Report, TR).
Unie bez ograniczeń typu
W Standardzie C++ istnieją restrykcje nakładane na dopuszczalny typ składowy unii. Na przykład unie nie mogą zawierać żadnych obiektów z nietrywialnymi konstruktorami. Wiele z tych restrykcji wydaje się być niepotrzebne, więc w następnym Standardzie będą one usunięte z wyjątkiem typów referencyjnych. Te zmiany spowodują, że unie będą przydatniejsze i łatwiejsze w użyciu[10].
Prosty przykład unii dozwolonej w C++11:
struct punkt
{
punkt() {}
punkt(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union
{
int z;
double w;
punkt p; // Nielegalne w C++ bo istnieje nietrywialny konstruktor. Legalne w C++11.
};
Te zmiany nie spowodują kłopotów kompatybilnościowych z istniejącym kodem, ponieważ dotyczą one tylko złagodzenia reguł.
Ulepszenia funkcjonalności w rdzeniu języka
Ulepszenia te mają na celu umożliwienie wyrażenia w języku takich rzeczy, które w poprzednich wydaniach albo nie były możliwe, trzeba było wielu linijek kodu lub wymagały użycia nieprzenośnych bibliotek.
Szablony ze zmienną listą parametrów
Szablony klas lub funkcji w poprzednich standardach C++, pobierały tylko określoną sekwencję argumentów. C++11 pozwala szablonom na pobieranie dowolnej liczby argumentów dowolnego typu.
template< typename... Values> class krotka;
Klasa szablonowa krotka będzie pobierać dowolną ilość nazw typów jako parametry szablonu:
class krotka<std::vector<int>, std::map<std::string, std::vector<int> > > jakasNazwaInstancji;
Liczba argumentów może być zerowa, więc class krotka<> jakasNazwaInstancji także będzie działać.
Zamiast zmiennego szablonu z zerową liczbą parametrów, równie dobrze można stosować:
template< typename First, typename... Rest> class krotka;
Zmienne szablony (szablony o zmiennej liczbie argumentów) mogą także być zastosowane do funkcji, wprowadzając do nich bezpieczny typowo mechanizm podobny do zmiennej listy argumentów w C:
template< typename... Params> void printf(const std::string &strFormat, Params... parameters);
Można tu zauważyć operator ... na prawo od typu Params w sygnaturze funkcji i na lewo od Params w specyfikacji szablonu. Kiedy operator ... jest na lewo od typu (jak w specyfikacji szablonu), wtedy jest to operator "pakujący". Oznacza, że liczba typów może być zero lub więcej. Kiedy operator jest na prawo od typu, jest to operator "rozpakowujący". Powoduje on powielenie operacji wykonywanych na danym typie; jedna na każdy spakowany typ. W powyższym przykładzie, funkcja printf będzie miała podany parametr dla każdego spakowanego typu w Params.
Użycie zmiennych szablonów jest często rekursywne. Same zmienne zmiennych szablonów nie są łatwo dostępne dla implementacji funkcji lub klasy. Typowym mechanizmem dla definicji czegoś na kształt zamiennika funkcji printf zaimplementowanego za pomocą zmiennego szablonu byłoby:
void printf(const char *s)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
throw std::runtime_error("nieprawidłowy format napisu: brakujące argumenty");
std::cout << *s++;
}
}
template< typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
while (*s)
{
if (*s == '%' && *(++s) != '%')
{
std::cout << value;
printf(*s ? ++s : s, args...); // wywołaj nawet gdy *s == 0 dla detekcji dodatkowych argumentów
return;
}
std::cout << *s++;
}
throw std::runtime_error("dodatkowe argumenty przekazane do printf");
}
Jest to wywołanie rekurencyjne. Warto tu zauważyć, że zmiennoszablonowa wersja printf wywołuje siebie samą lub, w przypadku pustego args, wywołuje pojedynczy przypadek.
Nie ma prostego mechanizmu przechodzenia (iterowania) po wszystkich argumentach zmiennego szablonu. Jednak przy użyciu operatora rozpakowującego, argumenty szablonu mogą być wirtualnie rozpakowane wszędzie.
Przykładowo, klasa może określać poniższe:
template < typename... BaseClasses> class ClassName : public BaseClasses...
{
public:
ClassName (BaseClasses&&... baseClasses) : BaseClasses(static_cast<BaseClasses&&>(baseClasses))... {}
}
Operator rozpakowujący może replikować typy dla klas bazowych ClassName tak, że ta klasa będzie dziedziczona z każdego przekazywanego typu. A także, konstruktor musi pobierać referencję do każdej klasy bazowej tak, aby zainicjować kasy bazowe ClassName.
Parametry zmiennych szablonów mogą być przekazywane za pomocą funkcji szablonowych. W połączeniu z r-referencjami można przekazywać parametry bez żadnych zmian:
template< typename TypeToConstruct> struct SharedPtrAllocator
{
template< typename ...Args> tr1::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params)
{
return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(static_cast<Args&&>(params)...));
}
}
Tu się rozpakowuje listę argumentów do konstruktora TypeToConstruct. Składnia static_cast<Args&&>(params) przekazuje do konstruktorów bez zmian argumenty dowolnych typów, nawet uwzględniając modyfikatory const. Operator rozpakowujący zastosuje składnię przekazującą do każdego parametru. Taka funkcja fabrykująca automatycznie zarządza zaalokowaną pamięcią w tr1::shared_ptr między innymi dla zapobiegania wyciekom pamięci.
Dodatkowo, liczba argumentów w paczce szablonowych parametrów może być określona następująco:
template< typename ...Args> struct SomeStruct
{
static const int size = sizeof...(Args);
}
Wynikiem SomeStruct<Type1, Type2>::size będzie 2, a dla SomeStruct<>::size będzie 0.
Nowe literały łańcuchowe
Starszy standard C++ oferuje dwa rodzaje literałów dla łańcuchów znaków. Pierwszy rodzaj, zawarty pomiędzy podwójnymi cudzysłowami, jest tablicą elementów typu const char z ostatnim elementem będącym znakiem null. Drugi rodzaj, zawarty pomiędzy podwójnymi cudzysłowami w L"", jest tablicą elementów typu const wchar_t z ostatnim elementem będącym znakiem null. Żaden z tych literałów nie obsługuje standardu literałów łańcuchowych kodowanych za pomocą Unicode.
W celu poszerzenia obsługi Unikodu w kompilatorach C++ definicja typu char została zmodyfikowana tak, aby wielkość tego typu była wystarczająca do zmieszczenia ośmiobitowego kodowania UTF-8 i aby była wystarczająco duża do zmieszczenia dowolnego elementu podstawowego zbioru zbioru wykonawczego. Poprzednio było to definiowane dopiero ostatnio.
Są trzy kodowania Unicode obsługiwane przez C++11: UTF-8, UTF-16 i UTF-32. Oprócz wspomnianych zmian w typie char, C++11 dodaje dwa nowe typy znakowe: char16_t i char32_t. Każdy z nich jest zaprojektowany do przechowywania odpowiednio: znaku UTF-16 i znaku UTF-32.
Sposób tworzenia nowych typów literałów łańcuchowych dla różnych kodowań:
u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."
Typem pierwszego literału jest const char[]
. Typ drugiego literału to const char16_t[]
. Natomiast typem trzeciego jest const char32_t[]
.
Podczas tworzenia literałów Unicode często przydaje się możliwość wstawiania numerów kodów Unicode bezpośrednio w samym literale. W C++11 robi się to następująco:
u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \u2018."
Numer po '\u' jest heksadecymalny; nie jest potrzebne poprzedzanie tego numeru prefiksem '0x'. Identyfikator '\u' reprezentuje 16-bitowy numer kodowy Unicode; w celu wstawienia 32-bitowego numeru kodowego, należy numer poprzedzić identyfikatorem '\U'. Tylko prawidłowe numery kodowe mogą być wpisane. Przykładowo, zakres numerów U+D800—U+DFFF jest zabroniony, ponieważ są one zarezerwowane w kodowaniach UTF-16 dla par zastępczych (ang. surrogate pair).
Czasem przydaje się możliwość uniknięcia interpretowania literałów łańcuchowych, szczególnie w plikach XML lub w językach skryptowych. C++11 umożliwia więc utworzenie surowych literałów łańcuchowych (ang. raw string literal):
R"(The String Data \ Stuff " )" R"delimiter(The String Data \ Stuff " )delimiter"
W pierwszym przypadku, wszystko pomiędzy nawiasami (
)
jest częścią napisu. Znaki "
i \
nie muszą być poprzedzane jakimś znakiem wyjścia. W drugim przypadku, "delimiter(
zaczyna napis, który kończy się tylko z chwilą napotkania na )delimiter"
. Napis delimiter"
może być dowolnym napisem, który pozwala użytkownikowi używać znaku )
wewnątrz surowego literału.
Surowe literały łańcuchowe mogą być złożone z szerokich znaków lub dowolnych literałów Unicode.
Literały definiowane przez użytkownika
Standard C++ definiuje wiele typów literałów. Znaki takie jak "12.5" stanowią literał o typie określanym przez kompilator jako typ double o wartości 12.5. Dodając przyrostek "f" (czyli mając "12.5f"), tworzy się typ float o wartości 12.5. Przyrostkowe modyfikatory literałów są definiowane tylko przez starsze specyfikacje C++ i użytkownik nie może dodawać nowych modyfikatorów. C++11 posiada taką możliwość.
Przy transformacji literałów wyróżnia się dwie fazy: surową (ang. raw) i gotową (ang. cooked). Surowy literał jest sekwencją znaków określonego typu, podczas gdy gotowy literał jest odrębnym typem. Literał C++ 1234 jako literał surowy jest sekwencją znaków '1', '2', '3', '4'. Jako gotowy literał, jest liczbą całkowitą 1234. Literał C++ 0xA w formie surowej jest sekwencją znaków '0', 'x', 'A', a w formie gotowej jest liczbą całkowitą 10.
Literały mogą być rozszerzone zarówno w formie surowej i gotowej, z wyjątkiem literałów łańcuchowych, które mogą być przetwarzane tylko w formiej gotowej. Wyjątek ten jest związany z tym, że łańcuchy posiadają przedrostki określające ich typ oraz typ ich znaków.
Wszystkie literały zdefiniowane przez użytkownika są przyrostkami; definiowanie przedrostków nie jest możliwe.
Literały użytkownika przetwarzające surowe formy literałów są zdefiniowane następująco:
OutputType operator"" Suffix(const char* literal_string);
OutputType someVariable = 1234Suffix;
Drugie wyrażenie wykonuje kod zdefiniowany przez funkcję literałową użytkownika. Do tej funkcji jest przekazywany napis w stylu C, więc jest zakończony znakiem null.
Alternatywnym mechanizmem przetwarzania surowych literałów jest użycie zmiennego szablonu (ang. variadic template):
template<char...> OutputType operator"" Suffix();
OutputType someVariable = 1234Suffix;
Tworzy się tu instancję funkcji przetwarzającej literał jako operator"" Suffix<'1', '2', '3', '4'>. W tej formie nie ma końcowego znaku null w napisie. Głównym celem takiego postępowania jest użycie słowa kluczowego C++11 constexpr i pozwolenie kompilatorowi na całkowite przetworzenie literału w czasie kompilacji; zakłada się, że OutputType jest konstruowany jako typ stałowyrażeniowy i kopiowalny i że funkcja przetwarzająca literał jest funkcją stałowyrażeniową (tj. typu constexpr).
Dla gotowych literałów tylko typ takiego literału jest w użyciu oraz nie ma alternatywnych form szablonowych:
OutputType operator"" Suffix(int the_value);
OutputType someVariable = 1234Suffix;
Dla literałów łańcuchowych:
OutputType operator"" Suffix(const char * string_values, size_t num_chars);
OutputType operator"" Suffix(const wchar_t * string_values, size_t num_chars);
OutputType operator"" Suffix(const char16_t * string_values, size_t num_chars);
OutputType operator"" Suffix(const char32_t * string_values, size_t num_chars);
OutputType someVariable = "1234"Suffix; //Wywołuje wersję const char *
OutputType someVariable = u8"1234"Suffix; //Wywołuje wersję const char *
OutputType someVariable = L"1234"Suffix; //Wywołuje wersję const wchar_t *
OutputType someVariable = u"1234"Suffix; //Wywołuje wersję const char16_t *
OutputType someVariable = U"1234"Suffix; //Wywołuje wersję const char32_t *
Literały znakowe są definiowane podobnie.
Wielozadaniowy model pamięci
Komitet standardu C++ planuje zestandaryzować obsługę programowania wielowątkowego.
Standaryzacja będzie prowadzona na dwóch polach: definicji modelu pamięci, który pozwoli na współegzystencję wielu wątków w programie oraz definicji obsługi interakcji pomiędzy wątkami. Ta interakcja będzie udostępniona poprzez biblioteki, zobacz ułatwienie używania wątków.
Model pamięci jest konieczny do określenia warunków, w których wiele wątków może mieć dostęp do tego samego miejsca w pamięci. Gwarantuje się, że program, który zastosuje się do tych reguł, wykona się poprawnie. Program, który nie zastosuje do tych reguł, może zachować się w sposób niezdefiniowany z powodu optymalizacji kodu wynikowego przez kompilator i problemów ze spójnością pamięci (ang. memory coherence).
Lokalna pamięć wątku
W wielowątkowym środowisku każdy wątek musi posiadać własne zmienne. Jest to już zapewnione dla lokalnych zmiennych w funkcji, ale nie jest tak w przypadku globalnych i statycznych zmiennych.
W C++11 zaproponowano, oprócz istniejących static, dynamic i automatic, nowy modyfikator czasu trwania zmiennej thread-local (pol. Lokalna Pamięć Wątku). Lokalna pamięć wątku będzie zaznaczona za pomocą thread_local
.
Dowolny obiekt mogący mieć statyczny czas trwania (tj. czas życia pokrywający się z czasem wykonywania całego programu), może mieć lokalnowątkowy czas trwania. W zamierzeniu, lokalnowątkowy obiekt może być inicjowany za pomocą konstruktora i niszczony za pomocą destruktora (tak, jak dla obiektów o statycznym czasie trwania).
Ustawianie metod jako default lub delete
W standardzie C++ kompilator dołącza do obiektów konstruktor domyślny, konstruktor kopiujący, operator przypisujący operator= jeśli użytkownik nie zdefiniuje swoich własnych wersji tych metod. C++ definiuje także kilka globalnych operatorów (takich jak operator= i operator new), które pracują ze wszystkimi klasami i które użytkownik także może zastąpić swoimi wersjami.
Problem polega na tym, że użytkownik ma małą kontrolę nad tworzeniem takich domyślnych metod. Na przykład stworzenie klasy niekopiowalnej wymaga deklaracji prywatnego konstruktora kopiującego i operatora przypisującego i niedefiniowanie ich. Próba użycia takich funkcji spowoduje błąd kompilatora lub linkera. Jednak nie jest to idealne rozwiązanie.
Dalej, w przypadku domyślnego konstruktora, mogłaby być przydatna możliwość jawnego przekazania kompilatorowi polecenia utworzenia go. Kompilator nie stworzy konstruktora domyślnego, jeśli obiekt posiada dowolne konstruktory. Jest to przydatne w wielu przypadkach, ale mogłoby być przydatne także posiadanie zarówno specjalizowanego konstruktora, jak i konstruktora domyślnego tworzonego przez kompilator.
C++11 pozwala na jawne użycie lub zaprzestania użycia tych standardowych metod. Na przykład poniższy typ jawnie deklaruje użycie domyślnego konstruktora:
struct SomeType
{
SomeType() = default; //Domyślny konstruktor jest jawnie określony.
SomeType(OtherType value);
};
Alternatywnie, pewne cechy mogą być jawnie zablokowane. Na przykład poniższy typ jest niekopiowalny:
struct NonCopyable
{
NonCopyable & operator=(const NonCopyable&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable() = default;
};
Typ może być niemożliwy do zaalokowania za pomocą operatora new:
struct NonNewable
{
void *operator new(std::size_t) = delete;
};
Taki obiekt może być alokowany tylko na stosie lub jako składowa innego obiektu. Nie może być alokowany na stercie bez nieprzenośnych sztuczek.
Specyfikator = delete
może być użyty do zablokowania wywołania dowolnej metody, co może być użyte do zablokowania wywołania metody z określonymi parametrami:
struct NoDouble
{
void f(int i);
void f(double) = delete;
};
Próba wywołania f()
z argumentem typu double
będzie odrzucona przez kompilator, zamiast wykonać niejawnej konwersji do int
. Można to uogólnić na zakazanie wywołania metod dowolnych typów różnych od int
następująco:
struct OnlyInt
{
void f(int i);
template<class T> void f(T) = delete;
};
Typ 'long long int'
W systemach 32-bitowych przydatnym jest typ całkowitoliczbowy long long
, który ma rozmiar co najmniej 64 bitów. Standard C99 wprowadził ten typ do języka C, a teraz jest od dłuższego już czasu obsługiwany przez większość kompilatorów C++. Niektóre kompilatory C++ obsługiwały ten typ nawet na długo przed wprowadzeniem go do C99. C++11 wprowadza ten typ do Standardu C++.
Typ long long int jest mniej użyteczny w systemach 64-bitowych, jako że przykładowy model wielkości danych w tych systemach to:
- 16 bit:
short int
- 32 bit:
int
- 64 bit:
long int
Niemniej jednak, w systemach 32-bitowych (a także w 64-bitowych systemach Windows, które używają modelu wielkości danych, gdzie typ long ma długość 32 bitów), jest głęboko zakorzenionym nawykiem używać typu long long int
jako całkowitoliczbowej zmiennej 64-bitowej.
Komitet C++ jest znany ze swej niechęci do standaryzacji nowych, podstawowych typów, jeśli nie zostały one zaakceptowane przez Komitet Języka C (który jest niezależny od Komitetu C++, ale w dużej mierze skład obu Komitetów pokrywa się). Jednak już teraz long long int
(w skrócie: long long
) stał się standardem de facto, a w C99 standardem de iure. Tak więc wydaje się, że ten stan rzeczy będzie zmieniony i Komitet C++ zaakceptuje long long int
jako typ podstawowy (razem z typem unsigned long long int
).
W przyszłości long long int
może być użyty do zmiennych całkowitych o długości 128 bitów, jeśli zajdzie taka potrzeba lub nowe procesory będą miały rejestry ogólnego stosowania o takiej szerokości.
Statyczne asercje
- Źródło[9]
Starszy standard C++ posiada dwie metody do testowania asercji: makro assert
i dyrektywa preprocesora #error
. Jednak żaden z nich nie jest odpowiedni do używania w szablonach: makro testuje asercje w czasie wykonywania kodu, a dyrektywa preprocesora testuje w fazie preprocesorowej, czyli przed tworzeniem instancji szablonów.
Nowe narzędzie wprowadza nowy sposób testowania asercji w czasie kompilacji, przy użyciu nowego słowa kluczowego static_assert
. Deklaracja przyjmuje następującą formę:
static_assert( stałe_wyrażenie, komunikat_błędu) ;
Kiedy stałe_wyrażenie jest fałszywe, wtedy kompilator wyrzuca komunikat_błędu. Poniżej pokazane są dwa przykłady używania static_assert
:
static_assert( 3.14 < GREEKPI && GREEKPI < 3.15, "GREEKPI is inaccurate!" ) ;
template< class T >
struct Check
{
static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;
Pierwszy przykład stanowi alternatywę dla dyrektywy preprocesora #error
; dla porównania, drugi przykład pokazuje jak asercja jest sprawdzana podczas każdego instancjonowania klasy szablonowej Check
.
Statyczne asercje są przydatne także poza szablonami. Przykładowo, jakaś szczególna implementacja algorytmu mogłaby zależeć od tego, aby rozmiar long
był większy niż rozmiar int
, czyli tego, czego Standard nie zapewnia.
'sizeof' działający na składowych klasy niezdefiniowanego jeszcze obiektu
W C++ operator sizeof może być użyty do typów lub obiektów, ale nie można go stosować jak poniżej:
struct SomeType { OtherType member; };
sizeof(SomeType::member); //Nie zadziała.
Tu powinno zwrócić rozmiar OtherType. C++03 nie pozwala na to, więc jest to błąd kompilacji. Natomiast w C++11 jest to dozwolone. Jest jednak możliwe pewne obejście w C++03:
struct SomeType { OtherType member; };
sizeof(static_cast<SomeType*>(0)->member);
Zmiany w bibliotece standardowej C++
Do standardowej biblioteki C++11 wprowadzono kilka zmian, które mogłyby być zaimplementowane również w starszych wersjach C++, ale część bazuje na nowych możliwościach rdzenia języka C++11.
Duża część nowych bibliotek jest zdefiniowana w dokumencie C++ Standards Committee's Library Technical Report (zwanym TR1), który opublikowano w 2005 roku. Różne pełne i częściowe implementacje TR1 są dostępne w przestrzeni nazw std::tr1
. W C++11 są one dostępne w przestrzeni std
. Chociaż implementacje TR1 są przeniesione do C++11, to jednak zostały one uaktualnione w związku z nowymi możliwościami języka C++11, które z kolei nie były dostępne w pierwszych wersjach TR1.
Komitet zamierza stworzyć drugą wersję dokumentu TR1, zwaną Technical Report 2 (TR2), po ukończonej standaryzacji C++11. Te proponowane funkcjonalności biblioteczne, które nie były gotowe na czas przed ukończeniem standaryzacji C++11, będą przeniesione do TR2 lub będą przekazane do dalszego rozpatrywania.
Aktualizacja komponentów standardowej biblioteki
C++11 oferuje liczne nowe możliwości językowe, z których mogą skorzystać komponenty obecnie istniejącej biblioteki standardowej. Przykładowo, większość komponentów biblioteki może skorzystać na obsłudze konstruktora przenoszącego opartego na r-referencji. Korzyść może wynikać z szybkiego przenoszenia ciężkich kontenerów, jak i z szybkiego przenoszenia ich zawartości pod nowy adres pamięci. Komponenty biblioteki standardowej będą uaktualnione wraz z nowymi możliwościami języka C++11 tam, gdzie będzie to rozsądne. Wśród tych nowych możliwości języka, z których komponenty skorzystają, są:
- R-referencje i związana z tym obsługa przenoszenia,
- Obsługa kodowania UTF-16 i UTF-32,
- Zmienne szablony (razem z r-referencję dla pełnego przekazywania argumentów),
- Wyrażenia stałe w fazie kompilacji,
- decltype,
- Jawne operatory konwersji,
- Metody ze specyfikatorami default i delete.
Dodatkowo, sporo czasu minęło od ostatniej standaryzacji C++ i dużo kodu jest napisanego w oparciu o obecną standardową bibliotekę. W związku z tym można wskazać te części biblioteki standardowej, które mogłyby być ulepszone. Spośród wielu rozważanych obszarów ulepszeń są alokatory standardowej biblioteki i w ramach uzupełnienia obecnego modelu alokatorów będzie wprowadzony nowy model oparty na zasięgu.
Ułatwienie używania wątków
W C++11 sam język dostarcza nowy model pamięci obsługujący wątki, ale w głównej mierze umożliwienie wielowątkowego pisania programów będzie zasługą biblioteki standardowej C++11.
Została dodana klasa wątku, która pobiera jako argument obiekt funkcyjny do uruchomienia w nowym wątku. Możliwe jest zamrażanie wątku, dopóki inny wątek nie zakończy swego działania, czyli dołączanie wątku (ang. thread joining). Dostęp do operacji specyficznych dla danej platformy jest możliwy wszędzie tam, gdzie jest to osiągalne.
Dla synchronizacji pomiędzy wątkami do biblioteki zostały dodane też muteksy i monitory. Są one dostępne, dla łatwiejszego użycia, poprzez blokady RAII i algorytmy zatrzaskowe.
W zastosowaniach wysokowydajnościowych i jednocześnie niskopoziomowych konieczna jest czasem komunikacja pomiędzy wątkami bez narzutu związanego z użyciem muteksów. Jest to osiągalne za pomocą operacji atomowych na lokacjach pamięci razem z odpowiednimi barierami pamięci. Biblioteka operacji atomowych pozwala na określanie minimalnej synchronizacji koniecznej dla jakiejś operacji.
Wysokopoziomowe operacje wątkowe, szczególnie przyszłości (ang. futures) i zasobniki z wątkami (ang. thread pools) są ideami odłożonymi do następnego Raportu Technicznego C++ (ang. Technical Report). Nie będą częścią C++11, ale końcowa ich implementacja jest spodziewana jako nadbudowa dla funkcji z biblioteki wątkowej.
Typy krotkowe
Krotki mogą być uważane za uogólnienie składowych struktur.
Wersja C++11 krotki TR1 korzysta z nowych możliwości języka, takich jak zmienne szablony. Wersja TR1 wymaga zdefiniowanej na etapie implementacji maksymalnej liczby zawartych typów i wymaga stosowania sztuczek z makrami. Dla porównania, implementacja wersji z C++11 nie wymaga jawnie zdefiniowanej maksymalnej liczby typów. Chociaż kompilatory prawie zawsze posiadają wewnętrzne ograniczenie maksymalnej głębokości rekursji dla tworzenia instancji szablonów (co jest normalne), to wersja krotek w C++11 nie pokazuje tej wartości użytkownikowi.
Używając zmiennych szablonów, definicja klasy krotkowej wygląda następująco:
template <class ...Types> class tuple;
Przykład definicji i używania typu krotkowego:
typedef tuple< int, double, long &, const char * > test_tuple ;
long lengthy = 12 ;
test_tuple proof( 18, 6.5, lengthy, "Ciao!" ) ;
lengthy = get<0>(proof) ; // Przypisz do 'lengthy' wartość 18.
get<3>(proof) = " Beautiful!" ; // Modyfikuj czwarty element krotkowy.
Możliwe jest stworzenie krotki proof
bez definicji jej zawartości, ale tylko wtedy, gdy typy elementów krotek posiadają konstruktory domyślne. Co więcej, jest możliwe przypisanie jednej krotki drugiej: jeśli typy tych krotek są takie same, to jest wtedy konieczne aby każdy typ elementów miał konstruktor kopiujący. Jeśli nie są takie same, to wtedy konieczne jest, aby każdy typ elementu krotki będącej po prawej stronie przypisania był konwertowalny do typu odpowiadającego elementu krotki po lewej stronie, lub by odpowiadający element krotki po lewej stronie posiadał odpowiedni konstruktor.
typedef tuple< int , double, string > tuple_1 t1 ;
typedef tuple< char, short , const char * > tuple_2 t2( 'X', 2, "Hola!" ) ;
t1 = t2 ; // Ok, pierwsze dwa elementy mogą być skonwertowane,
// trzeci może być konstruowany z 'const char *'.
Operatory relacji (pomiędzy krotkami o tej samej liczbie elementów) oraz dwa wyrażenia do określania własności krotek są dostępne (tylko w czasie kompilacji):
tuple_size<T>::value
zwraca liczbę elementów krotkiT
,tuple_element<I, T>::type
zwraca typ obiektu numerI
krotkiT
.
Tablice haszujące
Jedną z najczęstszych sugestii zmian w bibliotece standardowej C++ było włączenie do niej tablic mieszających (nieuporządkowanych kontenerów asocjacyjnych). Nie były one włączone do obecnego standardu C++ tylko ze względu na ograniczenia czasowe. Chociaż takie rozwiązanie jest mniej efektywne niż zbalansowane drzewa w najgorszym przypadku (w przypadku wielu kolizji), to jednak średnia wydajność jest lepsza.
Kolizje są kontrolowane tylko za pomocą metody łańcuchowej, ponieważ Komitet nie uważa za stosowne standaryzować rozwiązanie wykorzystujące adresowanie otwarte, które wprowadza dość dużo związanych z nim problemów (przede wszystkim wtedy, gdy usuwanie elementów jest dopuszczalne).
W celu uniknięcia kolizji nazw z niestandardowymi bibliotekami z ich własnymi implementacjami tablic mieszających, używany jest przedrostek “unordered” zamiast "hash".
Ta część biblioteki standardowej będzie mieć cztery typy tablic mieszających, różniących się tym czy akceptują lub nie, elementy z takimi samymi kluczami (kluczami unikalnymi albo równoważnymi), i czy mapują one każdy klucz do powiązanej wartości.
Typ tablicy haszującej | Dowolnie mapowany typ | Klucze równoważne |
---|---|---|
unordered_set | ||
unordered_multiset | • | |
unordered_map | • | |
unordered_multimap | • | • |
Nowe klasy spełniają wszystkie wymagania klasy kontenerów z STL i mają wszystkie metody dostępowe do elementów: insert
, erase
, begin
i end
.
Ta część biblioteki standardowej nie potrzebuje żadnych rozszerzeń rdzenia języka C++ (aczkolwiek implementacja skorzysta na nich). Jest tylko małe rozszerzenie pliku nagłówkowego <functional>
i wprowadzenie nowych plików nagłówkowych <unordered_set>
i <unordered_map>
. Żadne inne zmiany do istniejących klas standardowych nie są potrzebne oraz nie zależą one od innych rozszerzeń biblioteki standardowej.
Wyrażenia regularne
Wiele mniej lub bardziej standaryzowanych bibliotek zostało stworzonych do obsługi wyrażeń regularnych. Ponieważ wyrażenia te są bardzo popularne, to standardowa biblioteka je obsługuje, używając wszystkich zalet obiektowego języka programowania.
Nowa biblioteka, definiowana w nowym pliku nagłówkowym <regex>
, składa się z kilku klas:
- Wyrażenia regularne są reprezentowane przez instancje klasy szablonowej
basic_regex
, - Wystąpienia są reprezentowane przez instancje klasy szablonowej
match_results
.
Funkcja regex_search
jest używana do przeszukiwania, a funkcja regex_replace
do zamiany. Ta ostatnia funkcja zwraca nowy napis. Algorytmy regex_search
i regex_replace
przyjmują wyrażenie regularne i napis i zapisują wystąpienia znalezione w strukturze match_results
.
Tu jest przykład używania match_results
:
const char *reg_esp = "[ ,.\\t\\n;:]" ; // Lista znaków rozdzielających.
regex rgx(reg_esp) ; // 'regex' jest instancją klasy szablonowej
// 'basic_regex' z argumentem typu 'char'.
cmatch match ; // 'cmatch' jest instancją klasy szablonowej
// 'match_results' z argumentem typu 'const char *'.
const char *target = "Polytechnic University of Turin " ;
//Identyfikuje wszystkie słowa w 'target' rozdzielone przez znaki w 'reg_esp'.
if( regex_search( target, match, rgx ) )
{
//Jeśli istnieje słowo oddzielone przez podane znaki
for( int a = 0 ; a < match.size() ; a++ )
{
string str( match[a].first, match[a].second ) ;
cout << str << "\n" ;
}
}
Warto zauważyć użycie podwójnych znaków odwróconego ukośnika (ang. backslash, '\'), ponieważ preprocesor C++ używa tych znaków jako znaków wyjścia. W celu obejścia tego problemu można użyć surowych łańcuchów z C++11.
Biblioteka “regex
” nie potrzebuje modyfikacji istniejących plików nagłówkowych ani żadnych rozszerzeń w rdzeniu języka.
Sprytne wskaźniki ogólnego przeznaczenia
Wskaźniki te są zapożyczone z TR1. Współdzielony wskaźnik shared_ptr
jest wskaźnikiem ze zliczaniem referencji, który naśladuje maksymalnie, jak to tylko możliwe, zwykłe wskaźniki C++. W implementacji TR1 brakowało pewnych własności wskaźników, takich jak wielokrotne nazwy (ang. aliasing) i arytmetyka wskaźników. W C++11 te braki są uzupełnione.
Współdzielony wskaźnik automatycznie niszczy swoją zawartość tylko, jeśli nie ma już współdzielonych wskaźników odnoszących się do obiektu początkowo tworzonego dla współdzielonego wskaźnika.
Słaby wskaźnik weak_ptr
jest referencją do shared_ptr
która może określać, czy shared_ptr
był kasowany czy też nie. Sam weak_ptr
nie ma na celu zachowywanie się jak zwykły wskaźnik C++; po prostu jest obiektem i dostęp do faktycznego wskaźnika wymaga stworzenia obiektu shared_ptr
. Wskaźnik weak_ptr
nie posiada tego obiektu, na którego wskazuje i dlatego obecność weak_ptr
nie zapobiega niszczenia obiektu.
Poniżej podano przykład używania shared_ptr
:
int main( )
{
shared_ptr<double> p_first(new double) ;
{
shared_ptr<double> p_copy = p_first ;
*p_copy = 21.2 ;
} // Niszczenie 'p_copy' ale nie zaalokowanego double
return 0; // Niszczenie 'p_first' i odpowiadającego zaalokowanego double
}
unique_ptr
będzie zamiennikiem auto_ptr
, który z kolei będzie oznaczony jako odradzany (ang. deprecated). Ma wszystkie możliwości auto_ptr
z wyjątkiem niebezpiecznego niejawnego przenoszenia z l-wartości. W przeciwieństwie do auto_ptr
, unique_ptr
może być stosowany z kontenerami C++11 uwzględniającymi przenoszenie.
Rozszerzalne mechanizmy generowania liczb losowych
Niektóre aplikacje potrzebują niedeterministycznego zachowania (nawet jeśli pozornego) w postaci generacji liczb losowych.
W C++98 jedynym istniejącym standardowym narzędziem do tego była funkcja rand
, ale nie jest ona dobrze zdefiniowana i jej algorytm w całości zależy od producentów biblioteki. Nowe narzędzie do generatorów liczb losowych jest zdefiniowane przez plik nagłówkowy <random>
.
Generatory liczb losowych posiadają wewnętrzny stan i funkcję, która oblicza rezultat i wysterowuje generator do następnego stanu. Te dwie charakterystyki stanowią silnik generatora. Inną bardzo ważną charakterystyką jest rozkład wyników lub raczej przedział i gęstość zmiennej losowej.
Można wybrać silniki i rozkłady definiowane przez standard albo stworzyć własne.
Silniki dla liczb pseudolosowych
Nowa biblioteka wprowadza kilka silników do generacji liczb pseudolosowych. Wszystkie są szablonami klas, pozwalając programiście na ich dostosowanie do własnych potrzeb. Wewnętrzny stan silnika pseudolosowego jest określony przez ziarno (ang. seed), który w ogólności może być zbiorem zmiennych.
Szablon klasy | jakość | szybkość | wielkość stanu* |
---|---|---|---|
linear_congruential_engine | niska | średnia | 1 |
subtract_with_carry_engine | średnia | duża | 25 |
mersenne_twister_engine | wysoka | duża | 624 |
* Przemnóż wartość dla rozmiaru bajtowego używanego typu.
Wydajność tego silnika może być zwiększona przez użycie szablonu klasy discard_block_engine
. Dla wygody, plik nagłówkowy <random>
definiuje też kilka standardowych silników. Przykładem jest klasa mt19937
będąca konkretyzacją szablonu klasy mersenne_twister_engine
:
typedef mersenne_twister_engine<uint_fast32_t, 32, 624, 397, 31, 0x9908b0df, 11, 0xffffffff, 7, 0x9d2c5680, 15, 0xefc60000, 18, 1812433253> mt19937;
Silniki dla niedeterministycznego generatora liczb losowych
Poprzez klasę random_device
można tworzyć liczby niedeterministyczne typu unsigned int
. Jego implementacja wymaga użycia sprzętu, którego wejście jest niezależne od systemu (na przykład niezsynchronizowany zewnętrzny licznik lub przetwornik) oraz tradycyjnego generatora liczb pseudolosowych do dodatkowego "ulosowienia" wyników.
Rozkład liczb losowych
Nowa biblioteka definiuje wiele typów rozkładów, od rozkładów jednostajnych do tych definiowanych przez teorię prawdopodobieństwa, między innymi:
uniform_int_distribution
,discrete_distribution
,bernoulli_distribution
,geometric_distribution
,poisson_distribution
,binomial_distribution
,uniform_real_distribution
,exponential_distribution
,normal_distribution
,gamma_distribution
.
Oczywiście programista może sam tworzyć instancje standardowych rozkładów lub może użyć własnych kompatybilnych rozkładów.
Poniżej jest prosty przykład użycia nowej biblioteki:
uniform_int_distribution<int> distribution(0, 99);
mt19937 engine ;
int random = distribution(engine); // Przypisz wartość pomiędzy 0 i 99.
Referencja adapterowa
Referencja adapterowa jest uzyskiwana z instancji klasy szablonowej reference_wrapper
. Referencje adapterowe są podobne do zwykłych referencji ('&') w języku C++. W celu uzyskania referencji adapterowych z dowolnego obiektu klasy szablonowej, używa się ref
(a dla stałych referencji - cref
).
Referencje adapterowe są przydatne przede wszystkich dla funkcji szablonowych, kiedy potrzebujemy uzyskać referencje do parametrów, zamiast do ich kopii:
// Ta funkcja uzyskuje referencję do parametru 'r' i zwiększa go.
void f( int &r ) { r++ ; }
// Funkcja szablonowa.
template< class F, class P > void g( F f, P t ) { f(t) ; }
int main()
{
int i = 0 ;
g( f, i ) ; // 'g<void ( int &r ), int>' jest instancjonowana
// wtedy 'i' nie będzie modyfikowane.
cout << i << endl ; // Wyjście -> 0
g( f, ref(i) ) ; // 'g<void(int &r),reference_wrapper<int>>' jest instancjonowana
// wtedy 'i' będzie modyfikowane.
cout << i << endl ; // Wyjście -> 1
}
To narzędzie będzie dodane do istniejącego pliku nagłówkowego <utility>
i nie potrzebuje dalszych rozszerzeń języka C++.
Polimorficzne adaptery dla obiektów funkcyjnych
Polimorficzne adaptery dla obiektów funkcyjnych (zwane także "polimorficznymi adapterami obiektów funkcyjnych") są podobne semantycznie i składniowo do wskaźników do funkcji, ale są mniej ściśle związane i mogą ogólnie odnosić się do wszystkiego, co może być wywołane (wskaźniki do funkcji, wskaźniki do metod lub funktory), których argumenty są kompatybilne z tymi w adapterze.
Poniższy przykład pozwala na zrozumienie ich własności:
function<int ( int, int )> pF ; // Tworzenie adaptera przy użyciu
// klasy szablonowej 'function'.
plus<int> add ; // 'plus' jest deklarowany jako 'template<class T> T plus( T, T ) ;'
// wtedy 'add' jest typu 'int add( int x, int y )'.
pF = &add ; // Przypisanie jest poprawne, ponieważ
// parametry i zwracany typ odpowiadają sobie.
int a = pF( 1, 2 ) ; // Uwaga: jeśli adapter 'pF' nie odnosi się do żadnej funkcji
// wtedy wyrzucany jest wyjątek 'bad_function_call'.
function<bool ( short, short )> pg ;
if( pg == NULL ) // Jest zawsze prawdą ponieważ 'pg'
// nie jest jeszcze przypisana do żadnej funkcji.
{
bool adjacent( long x, long y ) ;
pg = &adjacent ; // Parametry i zwracana wartość są kompatybilne i
// przypisanie jest poprawne.
struct test
{
bool operator()( short x, short y ) ;
} car ;
pg = ref(car) ; // 'ref' jest funkcją szablonową zwracającą adapter
// metody 'operator()' struktury 'car'.
}
pF = pg ; // Jest to poprawne, ponieważ parametry i wartość zwracana
// adaptera 'pg' są kompatybilne z tymi w adapterze 'pF'.
Cechy typów dla metaprogramowania
Używając metaprogramowania, tworzy się program, który tworzy lub modyfikuje inny program (albo samego siebie). Dzieje się to w czasie kompilacji lub w czasie wykonywania. C++ Standards Committee zdecydował wprowadzić bibliotekę pozwalającą na metaprogramowanie szablonami podczas kompilacji.
Niżej zaprezentowano, co jest możliwe przy użyciu obecnego standardu C++ i przy pomocy metaprogramowania: obliczanie wykładnika przy użyciu rekursji instancji szablonu:
template< int B, int N >
struct Pow
{
// rekursywne wywołanie i rekombinacja.
enum{ value = B*Pow< B, N-1 >::value } ;
} ;
template< int B > struct Pow< B, 0 > // ''N == 0'' warunek zakończenia.
{
enum{ value = 1 } ;
} ;
int quartic_of_three = Pow< 3, 4 >::value ;
Wiele algorytmów może operować na różnych typach danych; szablony C++ umożliwiają programowanie generyczne i czynią kod bardziej zwięzłym. Niemniej jednak często w algorytmach zachodzi potrzeba uzyskania informacji o używanych właśnie typach. Takie informacje mogą być uzyskane podczas instancjonowania klas szablonowych przy użyciu cech typów.
Cechy typów mogą identyfikować kategorię obiektu i wszystkie własności klasy lub struktury. Są one zdefiniowane w nowym pliku nagłówkowym <type_traits>
.
W następnym przykładzie jest funkcja szablonowa "elaborate", która w zależności od typów danych będzie instancjonować jeden z dwóch proponowanych algorytmów (algorithm.do_it
):
// Pierwszy sposób.
template< bool B > struct algorithm
{
template< class T1, class T2 > int do_it( T1 &, T2 & ) { /*...*/ }
} ;
// Drugi sposób.
template<> struct algorithm<true>
{
template< class T1, class T2 > int do_it()( T1 *, T2 * ) { /*...*/ }
} ;
// Instancjonowanie 'elaborate' automatycznie instancjuje odpowiedni sposób.
template< class T1, class T2 > int elaborate( T1 A, T2 B )
{
// Użyj drugiego sposobu tylko jeśli 'T1' jest całkowite i 'T2' jest
// zmiennoprzecinkowe; w przeciwnym razie użyj pierwszego sposobu.
return algorithm< is_integral<T1>::value && is_floating_point<T2>::value >::do_it( A, B ) ;
}
Przez cechy typów zdefiniowane w <type_transform>
, jest także możliwe tworzenie operacji transformujących typ (static_cast
i const_cast
są niewystarczające wewnątrz szablonu).
Ten typ programowania tworzy elegancki i zwięzły kod; jednak słabą stroną tych technik jest debugowanie: niewygodne w czasie kompilacji i bardzo trudne w czasie wykonywania programu.
Jednolite metody ustalenia typów zwracanych wartości w obiektach funkcyjnych
Określanie zwracanego typu szablonowego obiektu funkcyjnego w fazie kompilacji nie jest intuicyjne, szczególnie jeśli typ zwracany zależy od parametrów funkcji. Jako przykład:
struct clear
{
int operator()( int ) ; // Typ parametru jest
double operator()( double ) ; // taki sam jak zwracana wartość.
} ;
template< class Obj > class calculus
{
public:
template< class Arg > Arg operator()( Arg& a ) const
{
return member(a) ;
}
private:
Obj member ;
} ;
Podczas tworzenia instancji klasy szablonowej calculus<clear>
, obiekt funkcyjny calculus
będzie posiadał zawsze taki sam zwracany typ jak obiekt funkcyjny clear
. Jednak dla poniższej klasy confused
:
struct confused
{
double operator()( int ) ; // Typ parametru NIE jest
int operator()( double ) ; // taki sam jak zwracana wartość
} ;
tworzenie instancji calculus<confused>
spowoduje, że zwracany typ calculus
nie będzie taki sam jak w klasie confused
. Kompilator może wygenerować ostrzeżenia o konwersji pomiędzy int
i double
.
TR1 wprowadza, a C++11 adoptuje, klasę szablonową std::result_of
która pozwala na określenie i użycie zwracanego typu z obiektu funkcyjnego dla każdej deklaracji. Obiekt calculus_ver2
używa obiektu std::result_of
do określenie zwracanego typu obiektu funkcyjnego:
template< class Obj >
class calculus_ver2
{
public:
template< class Arg >
typename std::result_of<Obj(Arg)>::type operator()( Arg& a ) const
{
return member(a) ;
}
private:
Obj member ;
} ;
W ten sposób w instancjach obiektów funkcyjnych calculus_ver2<confused>
nie ma konwersji, ostrzeżeń lub błędów.
Jedyną zmianą w std::result_of
w stosunku do wersji z TR1 jest to, że wersja TR1 pozwalała implementacji na porażkę w rozpoznaniu zwracanego typu wywołania funkcji. W związku ze zmianami w C++ w obsłudze decltype, wersja C++11 klasy std::result_of
nie potrzebuje więcej tych specjalnych przypadków; wymaga się od implementacji określania typu we wszystkich przypadkach.
Koncepty
Według początkowych założeń, C++11 miał wprowadzać funkcjonalność tzw. konceptów, jednak 13 lipca 2009 komitet odpowiadający za standard C++11 przegłosował ich usunięcie[12][13].
Motywacja do wprowadzenia konceptów
Klasy i funkcje szablonowe w C++ narzucają pewne wymagania odnośnie do przyjmowanych typów. Przykładowo, kontenery STL wymagają, aby przyjmowany typ mógł być konstruowany bez argumentów (np. aby miał konstruktor domyślny). W przypadku dynamicznego polimorfizmu, hierarchia dziedziczenia klas wskazuje, co może być argumentem jakiejś metody. I jeśli typem przyjmowanym przez taką metodę jest np. Foo&, to ta funkcja przyjmie też typy pochodne Foo& w danej hierarchii dziedziczenia klas. Natomiast w przypadku szablonów akceptowany jest dowolny obiekt, pod warunkiem, że dany obiekt posiada wymagane przez funkcję lub klasę szablonową metody (np. konstruktor kopiujący). Tak więc, jest czasem trudne szybko stwierdzić, jakie są wymagania względem obiektu ze strony tych funkcji lub klas szablonowych. C++11 miał dostarczyć mechanizm konceptów, służących do jawnego opisania takich wymagań.
Główną motywacją do wprowadzenia konceptów była chęć uproszczenia komunikatów błędów mogących wystąpić w trakcie kompilacji szablonów. Jeśli programista przekaże do jakiegoś szablonu taki obiekt, który nie spełnia wszystkich wymagań tego szablonu, wtedy kompilator wyświetli błędy kompilacji. Jednak komunikaty te są trudne do zrozumienia, szczególnie początkującym. Są dwie główne przyczyny tego stanu rzeczy. Po pierwsze, komunikaty błędu wyrzucają wszystkie parametry szablonów w pełnej postaci, co często prowadzi do bardzo rozległych komunikatów błędu. W niektórych kompilatorach proste błędy prowadzą do kilkukilobajtowych komunikatów błędów. Po drugie, te komunikaty często pokazują wtórną przyczynę błędu. Jeśli np. programista chce utworzyć std::vector z obiektami bez konstruktora kopiującego, wtedy prawie zawsze komunikaty odnoszą się najpierw do miejsca w klasie std::vector w miejscu pierwszego użycia konstruktora kopiującego. Wtedy programista musi mieć wystarczającą wiedzę o C++ aby zrozumieć, że przyczyną błędu jest brak rzeczonego konstruktora kopiującego w użytych obiektach.
Do rozwiązania tego problemu C++11 miał wprowadzić koncepty. Podobnie jak OOP używa klas bazowych do definiowania ograniczeń nałożonych na typ, koncepty byłyby nazwaną konstrukcją określającą, co typ musi mieć. Jednak w przeciwieństwie do OOP, definicja konceptu nie byłaby zawsze jawnie powiązana z typem przekazywanym do szablonu; jest powiązana z samą definicją szablonu.
Przyczyny usunięcia konceptów ze standardu
Koncepty już w 2008 roku zostały przez niektórych członków komitetu standaryzacyjnego uznane za "niewypróbowane, ryzykowne i kontrowersyjne". Ostatecznie o zarzuceniu tego projektu zdecydował tekst Bjarnego Stroustrupa Simplifying the Use of Concepts ("uproszczenie użycia konceptów")[13], w którym postulował daleko idące uproszczenia konceptów, nazywając je "główną porażką C++0x"[14] (sprzeciwiał się jednak całkowitemu usunięciu konceptów, choć uznawał tę opcję za lepszą od kontynuowania nad nimi pracy w ich ówczesnej postaci[15]). Powodami, które ostatecznie doprowadziły do wyeliminowania konceptów ze standardu były:
- komplikacja kodu (koncepty miały uprościć pisanie kodu, jednak zamiast tego przenosiły jedynie złożoność z szablonów na koncepty)[16],
- ilość czasu potrzebna na ukończenie konceptów (koncepty były rozwijane przez siedem lat, a mimo to nie były gotowe)[16],
- długi czas kompilacji[13],
- zmniejszenie wydajności programów[13].
Komitet uznał koncepty za niespełniające swojej funkcji i nie miał pomysłu na ich naprawę, która i tak zajęłaby co najmniej kilka lat[13].
Skutki decyzji komitetu
Wiele innych elementów standardu C++11 zakładało istnienie konceptów, co oznacza, że będą musiały zostać napisane od nowa[16]. Niektórzy członkowie komitetu uważają jednak, że C++ będzie miał koncepty w ciągu najbliższych pięciu lat (stan na 2009 rok)[13].
Artykuły
- The C++ Source Bjarne Stroustrup (2 January, 2006) A Brief Look at C++0x
- C/C++ Users Journal Bjarne Stroustrup (May, 2005) The Design of C++0x: Reinforcing C++’s proven strengths, while moving into the future
- Web Log di Raffaele Rialdi (September 16, 2005) Il futuro di C++ raccontato da Herb Sutter
- Informit.com (August 5, 2006) The Explicit Conversion Operators Proposal
- Informit.com (July 25, 2006) Introducing the Lambda Library
- Dr. Dobb's Portal Pete Becker (April 11, 2006) Regular Expressions TR1's regex implementation
- Informit.com (July 25, 2006) The Type Traits Library
- Dr. Dobb's Portal Pete Becker (May 11, 2005) C++ Function Objects in TR1
Zobacz też
- ConceptGCC
Przypisy
- ↑ ISO/IEC 14882:2011 Programming languages -- C++. ISO/IEC. [dostęp 2019-08-02]. (ang.).
- ↑ Working Draft, Standard for Programming Language C++. open-std.org. [dostęp 2019-08-02]. (ang.).
- ↑ Advanced search for standards and/or projects - ISO. www.iso.org. [dostęp 2015-07-20].
- ↑ ISO/IEC 14882:2017. [dostęp 2018-02-22].
- ↑ a b c d Danny Kalev: The Biggest Changes in C++11 (and Why You Should Care). blog.smartbear.com, 2011-06-20. [dostęp 2015-07-16]. (ang.).
- ↑ Danny Kalev: Using constexpr to Improve Security, Performance and Encapsulation in C++. blog.smartbear.com, 2012-12-19. [dostęp 2015-07-16]. (ang.).
- ↑ consteval specifier (since C++20). cppreference.com. [dostęp 2021-01-17].
- ↑ Danny Kalev: Get to Know the New C++11 Initialization Forms. Inform IT, 2012-03-28. [dostęp 2015-07-16]. (ang.).
- ↑ a b c d e f g Marius Bancila: Ten C++11 Features Every C++ Developer Should Use. Code Project, 2013-04-02. [dostęp 2015-07-16]. (ang.).
- ↑ N2544
- ↑ Wskaźniki inteligentne (Modern C++). msdn.nicrosoft.com. [dostęp 2015-07-16]. (pol.).
- ↑ James Iry: Concepts Get Voted Off The C++0x Island. Lambda the Ultimate, 2009-07-20. [dostęp 2009-08-30]. (ang.).
- ↑ a b c d e f Denny Kalev: The Removal of Concepts From C++0x. InformIT, 2009-07-23. [dostęp 2009-08-30]. (ang.).
- ↑ Bjarne Stroustrup: Simplifying the use of concepts. Open Standards, 2009-06-21. s. 23-24. [dostęp 2009-08-30]. (ang.).
- ↑ Bjarne Stroustrup: The C++0x "Remove Concepts" Decision. Dr. Dobb's, 2009-07-22. [dostęp 2010-01-06]. (ang.).
- ↑ a b c Danny Kalev: The Rise and Fall of C++0x Concepts. DevX.com, 2009-07-21. [dostęp 2009-08-30]. (ang.).
Linki zewnętrzne
- The C++ Standards Committee (ang.)
- Bjarne Stroustrup's homepage (ang.)
- C++0X: The New Face of Standard C++. informit.com. [zarchiwizowane z tego adresu (2008-02-21)]. (ang.)
- Herb Sutter's blog coverage of C++0X (ang.)
- A talk on C++0x given by Bjarne Stroustrup at the University of Waterloo. csclub.uwaterloo.ca. [zarchiwizowane z tego adresu (2009-01-23)]. (ang.)
- A quick and dirty introduction to C++0x (as of November 2007)