Nowe elementy składni C++11¶
>>
jako sekwencja zamykająca zagnieżdżone szablony¶
W C++11 sekwencja znaków >>
umożliwia zamknięcie zagnieżdżonych szablonów:
std::set<int, std::greater<int>> dict_desc; // OK w C++11
Można również używać dodatkowego separatora w postaci spacji:
std::set<int, std::greater<int> > dict; // OK w C++11, OK w C++98
Jeżeli chcemy użyć w definicji szablonu operatora przesunięcia bitowego w prawo, należy go umieścić w nawiasach:
const int n = 8;
std::list<std::array<int, n>>2>> lst; // błąd w C++98,
// błąd w C++11
// (pierwsza sekwencja >> zamyka szablony)
std::list<std::array<int, (n>>2)>> lst; // OK w C++11
Nowe typy danych podstawowych¶
C++11 wprowadza kilka nowych fundamentalnych typów danych:
typ:
[unsigned|signed] long long [int]
Gwarancja minimum 64 bitów
Literał:
LL
typy dla znaków UTF16/UTF32:
char16_t
,char32_t
typ dla pustego wskaźnika
nullptr
:nullptr_t
nullptr - uniwersalny pusty wskaźnik¶
Nowe słowo kluczowe - nullptr
wartość dla wskaźników, które na nic nie wskazują (wartość 0)
bardziej czytelny i bezpieczniejszy odpowiednik stałej
NULL/0
posiada zdefiniowany przez standard typ -
std::nullptr_t
(zdefiniowany w pliku nagłówkowym<cstddef>
)
namespace std
{
typedef decltype(nullptr) nullptr_t;
}
Deklaracja pustych zmiennych wskaźnikowych od C++11 powinna wyglądać:
int* ptr_1 = nullptr;
// or
int* ptr_1{}; // p1 is set to 0
nullptr
rozwiązuje problem z przeciążeniem funkcji przyjmujących jako argument wskaźnik lub typ całkowity:
void foo(int);
foo(0); // calls foo(int)
foo(NULL); // calls foo(int)
foo(nullptr); // compile-time error
void bar(int);
void bar(void*);
void bar(nullptr_t);
bar(0); // calls bar(int)
bar(NULL); // calls bar(int) if NULL is 0, ambiguous if NULL is 0L
bar(nullptr); // calls bar(void*) or bar (nullptr_t) if provided
Raw String Literals¶
W C++11 możemy uniknąć specjalnego traktowania „znaków ucieczki” (escape characters) w literałach znakowych stosując tzw. „Raw String Literals”.
„Raw string” zaczyna się od R"(
, a kończy się )"
.
std::string no_newlines = R"(\n\n)";
std::string cmd{R"(cd "C:\new folder\text")"};
Literały tekstowe mogą zawierać teraz kilka lini:
std::string with_newlines = R"(Line 1 of text...
Line 2...
Line 3)";
Aby mieć możliwość umieszczenia sekwencji )"
w literale „raw string”, należy użyć sekwencji przestankowej delim.
W rezultacie kompletna składnia literału „raw string” to: R"delim()delim"
.
sekwencja przestankowa może mieć długość do 16 znaków i nie może zawierać białych znaków (spacji itp.).
string str1 = R"raw(a\
b\nc()"
)raw";
string str2 = "a\\\n b\\nc()\"\n ";
Literały „raw string” są szczególnie przydatne przy definiowaniu wyrażeń regularnych lub ścieżek w systemie Windows.
std::regex re1( R"!("operator\(\)"|"operator->")!" ); // "operator()"|"operator->"
Wsparcie dla Unicode¶
Od C++11 dozwolone są następujące literały znakowe:
u8 - definiuje kodowanie UTF-8
u - definiuje literał ze znakami
char16_t
U - definiuje literał ze znakami
char32_t
u"UTF-16 string literal" // char16_t (UTF-16)
U"UTF-32 string literal" // char32_t (UTF-32)
u8"UTF-8 string literal" // char (UTF-8)
Kody znaków są podawane w postaci \unnnn
oraz \Unnnnnnnn
:
u8"G clef: \U0001D11E"
u"Euro: \u0024"
U"Skull and bones: \u2620"
Typy łańcuchowe¶
Standard definiuje cztery typy łańcuchowe:
std::string
- specjalizacja szablonustd::basic_string<char>
std::wstring
- specjalizacja szablonustd::basic_string<wchar_t>
std::u16string
- specjalizacja szablonustd::basic_string<char16_t>
std::u32string
- specjalizacja szablonustd::basic_string<char32_t>
Rozszerzone typy wyliczeniowe¶
Dla typów wyliczeniowych można w C++11 specyfikować typ całkowity na którym definiowane jest wyliczenie.
Wartości podawane w wyliczeniu muszą mieścić się w dozwolonym zakresie dla typu.
enum Coffee : std::uint_8_t { espresso, cappuccino, latte };
enum State : unsigned char { opened, closed, unknown = 999 }; // error!
Typ na bazie którego definiowane jest wyliczenie jest dostępny za pomocą metafunkcji std::underlying_type_t<EnumType>
:
Wyliczenia silnie typizowane - Scoped Enumerations¶
Dla wyliczeń silnie typizowanych (scoped enums):
można specyfikować na bazie jakiego typu definiowane jest wyliczenie
domyślnym typem na którym definiowane jest wyliczenie jest
int
niejawna konwersja do i z typu całkowitego nie jest dozwolona
nie ma możliwości porównania obiektów różnych typów wyliczeniowych
wartości wyliczenia są umieszczone w zakresie typu
istnieje możliwość użycia deklaracji zapowiadającej (forward declaration). Gdy używany jest tylko typ nie ma potrzeby rekompilacji przy dodaniu nowej wartości dla wyliczenia.
enum class Engine : char; // forward declaration
Engine e;
enum class Engine : char { petrol, diesel, wankel };
e = Engine::petrol; // OK
e = diesel; // error - no "diesel" in scope
e = 3; // error
e = static_cast<Engine>(1); // OK: e == Engine::diesel
int index = Engine::wankel; // error
underlying_type_t<Engine> index =
static_cast<underlying_type_t<Engine>>(Engine::wankel);
Zamiast enum class
może być użyte enum struct
- nie ma żadnej semantycznej różnicy między tymi formami.
Deklaracje typu z auto¶
Deklaracje zmiennych z użyciem słowa kluczowego auto
umożliwiają
automatyczną dedukcję typu zmiennej przez kompilator.
auto
jest słowem kluczowym, które w C++11 otrzymało nowe znaczenie.
w poprzednich standardach oznaczało zmienną automatyczną (tworzoną na stosie) - nigdy nie było używane
auto i = 42; // i : int
auto f = 3.14f; // f : float
std::set<std::string> spellcheck;
auto it = spellcheck.begin(); // it : std::set<std::string>::iterator
Definiując zmienną z użyciem auto
można dodawać modyfikatory const
, volatile
oraz stosować referencje lub wskaźniki:
auto i = 10; // i : int
const auto* ptr1 = &i; // ptr1 : const int*
const auto ptr2 = &i; // ptr2 : int* const
double f();
auto r1 = f(); // r1 : double
const auto& r2 = f(); // r2: const double&
const auto& ref_spellcheck = spellcheck; // ref_spellcheck : const std::set<std::string>&
Iteracja po kontenerach z auto¶
Używając auto
można wygodnie iterować po kontenerach zlecając kompilatorowi dedukcję typu iteratora.
Aby uzyskać w trakcie iteracji const_iterator
należy użyć nowych metod z interfejsu kontenerów standardowych cbegin()
i cend()
.
void do_something(int& x);
void print(const int& x);
vector<int> vec = { 1, 2, 3, 4, 5 };
for(auto it = vec.begin(); it != vec.end(); ++it)
{
do_something(*it); // ok - type of it - vector<int>::iterator
}
for(auto it = vec.cbegin(); it != vec.cend(); ++it)
{
print(*it); // ok - type of it - vector<int>::const_iterator
}
Mechanizm dedukcji typu dla auto¶
Mechanizm dedukcji typu dla auto
jest praktycznie taki sam jak dla parametrów szablonu.
template <typename T> void f(ParamType t);
f(expr); // dedukcja typu t na podstawie wyrażenia
auto i = expr; // praktycznie ten sam mechanizm dedukcji typu
W mechaniźmie automatycznej dedukcji typów specyfikator typu auto
jest odpowiednikiem ParamType
w mechaniźmie dedukcji typów dla szablonów.
Możemy wyróżnić trzy zasadnicze przypadki:
Specyfikator typu nie jest ani referencją ani wskaźnikiem.
jeśli wyrażenie inicjujące jest referencją - referencja jest usuwana
ignorowane są modyfikatory
const
ivolatile
tablice i funkcje rozpadają się do wskaźników (decay to pointers)
int x = 42; int& ref_x = x; const int& cref_x = x; auto a1 = x; // a1 : int auto a2 = ref_x; // a2 : int auto a3 = cref_x; // a3 : int int data[10]; auto ad = data; // ad : int* - array decays to pointer int foo(); auto af = foo; // af : int(*)() - function decays to pointer
Specyfikator typu jest referencją lub wskaźnikiem.
zachowywane są referencje oraz modyfikatory
const
ivolatile
tablice i funkcje nie rozpadają się do wskaźników
int x = 42; int& ref_x = x; const int& cref_x = x; auto& a1 = x; // a1 : int& auto& a2 = ref_x; // a2 : int& auto& a3 = cref_x; // a3 : const int& int data[10]; auto& ad = data; // ad : int(&)[10] int foo(); auto& af = foo; // af : int(&)()
Specyfikator typu jest „uniwersalną referencją”
jeśli wyrażenie inicjujące jest
l-value
następuje dedukcja to referencjil-value
jeśli wyrażenie inicjujące jest
r-value
następuje dedukcja to referencjir-value
int x = 42; auto&& ax1 = x; // ax1 : int& auto&& ax2 = 42; // ax2 : int&&
Ostrzeżenie
Jedyna różnica między mechanizmem dedukcji typu w szablonach a w auto
dotyczy
dedukcji typu z listy inicjalizacyjnej zdefiniowanej za pomocą nawiasów klamrowych { 1, 2, 3 }
.
Składnia definicji zmiennych z auto¶
Dozwolone są dwie składnie inicjalizacji:
składnia bezpośredniej inicjalizacji
auto var1(expr);
lub
auto var1{expr};
składnia kopiująca -
auto var2 = expr;
Obie składnie mają takie samo znaczenie w kontekście dedukcji typów.
Jeśli typ dedukowany ma konstruktor kopiujący zdefiniowany jako explicit
, to kompiluje się tylko składnia bezpośrednia.
// direct initialization syntax
int i= 10;
auto a = i;
auto b(i);
auto c{i}; // since C++17
// copy initialization syntax
struct Expl
{
Expl() {}
explicit Expl(const Expl&) {}
};
Expl e;
auto a1 = e; // compile error
auto a2{e}; // OK
Pętla for dla zakresów - range-based for¶
Pętla range-based for iteruje po wszystkich elementach zakresu.
Wyrażenie
for(for-range-declaration : for-range-initializer)
{
statement;
}
jest rozwijane do pętli:
{
auto &&__range = for-range-initializer;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin ) {
for-range-declaration = *__begin;
statement;
}
}
Wyrażenie begin-expr
i end-expr
są ewaluowane do:
__range.begin()
i__range.end()
lub z niższym priorytetem do
begin(__range)
iend(__range)
W praktyce pętla range-based for umożliwia wygodną iterację po:
kontenerach standardowych:
std::vector<int> vec = { 1, 2, 3, 4 };
for(const int& item : vec)
std::cout << item << " ";
tablicach typu C-style
int data[4] = { 1, 2, 3, 4 };
for(auto& n : data)
n *= 2;
liście inicjalizacyjnej
for(const auto& item : { 100, 200, 300, 400})
std::cout << item << " ";
Efektywna wersja range-based for¶
Kopiowanie elementów w trakcie iteracji może obniżyć wydajność (np. dla typów std::string
, std::shared_ptr
) lub może być zabronione np. std::unique_ptr
.
Można uniknąć kopiowania elementów w trakcie iteracji dodając referencję.
Opcjonalnie można również stosować modyfikator const
lub volatile
.
std::vector<std::shared_ptr<Gadget>> shared_gadgets;
// ...
for(const auto& ptr : shared_gadgets)
ptr->do_something();
std::vector<std::unique_ptr<Gadget>> unique_gadgets;
// ...
for(auto ptr : unique_gadgets) // compilation error
ptr->do_something();
for(const auto& ptr : unique_gadgets) // ok
ptr->do_something();
Iteracja po kontenerach zdefiniowanych przez użytkownika¶
Funkcje wolne begin()
oraz end()
są częścią biblioteki standardowej C++11 i umożliwiają
pętli range-based for iterację po tablicach natywnych (C-array).
Można też utworzyć własne implementacje tych funkcji adaptując struktury danych zdefiniowane przez użytkownika.
class MyContainer
{
//...
public:
friend std::list<int>::iterator begin(MyContainer& cont);
friend std::list<int>::iterator end(MyContainer& cont);
};
std::list<int>::iterator begin(MyContainer& cont)
{
return cont.values_.begin();
}
std::list<int>::iterator end(MyContainer& cont)
{
return cont.values_.end();
}
// ...
MyContainer cont;
for(const auto& item : cont)
std::cout << item << "\n";
Składnia jednolitej inicjalizacji¶
Motywacją dla wprowadzenia składni jednolitej inicjalizacji, był fakt, że inicjalizacja w C++98 stwarzała programistom wiele problemów.
Przykłady problemów w C++98:
int i; // undefined value
int var1(5); // "direct initialization" - from C++98
const int var2 = 10; // "copy initialization" - from C
int var3 = int(); // var3 becomes 0 - from C++98
int var4(); // function declaration!!!
int values[] = { 1, 2, 3, 4 }; // brace initialization
struct Point { int x, y; };
const Point pt1 = { 10, 20 }; // brace initialization
std::complex<double> c(4.0, 2.0); // initialization of classes
std::vector<std::string> cities; // no initialization for list of values
cities.push_back("Warsaw");
cities.push_back("Cracow");
Cele jednolitej inicjalizacji w C++11:
jeden uniwersalny sposób inicjalizacji danych
powinna istnieć możliwość inicjalizacji kontenerów standardowych
powinna zabraniać inicjalizacji z wykorzystaniem zawężających konwersji
Składnia jednolitej inicjalizacji jest rozszerzeniem standardu. Prawie cały kod wykorzystujący składnię C++98 jest wciąż poprawny. Jedyny wyjątek od tej reguły - niejawna konwersja, która zawęża typ.
Inicjalizacja z wykorzystaniem {}¶
Składnia inicjalizacji z wykorzystaniem nawiasów klamrowych jest teraz dozwolona we wszystkich przypadkach:
int i; // undefined value - still possible!!!
int var1{5};
const int var2{10};
int arr1[] = { 1, 2, var1, var1 + var2 };
const Point pt1{ 10, 20 };
std::complex<double> c{ 4.0, 2.0 };
const std::vector<std::string> cities = { "Wroclaw", "Cracow" };
std::map<int, std::string> = { {1, "one"}, {2, "two"} };
Można używać składni z klamrami do inicjalizacji pól na liście inicjalizacyjnej konstruktora oraz do inicjalizacji tablic dynamicznych.
class Data
{
static inline int id_gen_{0};
public:
Data() : id_{++id_gen_}, data_{ 1, 2, 3 }, names_{"Jan", "Adam", "Ewa"} {}
private:
const int id_;
int data_[3];
std::vector<std::string> names_;
};
int* buffer = new int[5] { 1, 5, var1, var2 + 10, -1 };
Inicjalizacja {} dla agregatów¶
Inicjalizacja agregatów za pomocą klamer przebiega dokładnie w ten sam sposób jak w C++98 i powoduje inicjalizację składowych wg kolejności ich definicji w agregacie. Liczba elementów na liście musi być równa lub mniejsza ilości elementów w agregacie.
std::array<int, 3> arr1 = {}; // [0, 0, 0]
std::array<int, 3> arr2 = { 1, 2 }; // [1, 2, 0]
std::array<int, 3> arr3 = { 1, 2, 3 }; // [1, 2, 3]
std::array<int, 3> arr4 = { 1, 2, 3, 4 }; // error
Inicjalizacja {} klas/struktur nie będących agregatami¶
Inicjalizacja {} klas/struktur nie będących agregatami powoduje wywołanie odpowiedniego konstruktora.
class Vector2D
{
public:
Vector2D(int x, int y) : x_{x}, y_{y}
{}
private:
int x_, int y_;
};
Vector2D vec1 { 10, 20 };
Vector2D vec2 = { 40, 20 };
Vector2D vec3 { 56, 33, 22 }; // error! too many args
Vector2D versor_x()
{
return { 1, 0 };
}
Składnia „kopiująca” T variable = expr
nie może wywołać konstruktora zdefiniowanego jako explicit
:
std::unique_ptr<int> ptr1 = { new int{10} }; // error
std::unique_ptr<int> ptr2 { new int{12} }; // OK
Konwersja zawężająca typ¶
C++11 nie zezwala na użycie w inicjalizacji klamrowej niejawnej konwersji, która może doprowadzić do zawężenia typu.
int arr1[] = { 1, 2, 4.5 }; // OK in C++98; error in C++11
int arr2[] = { 1, 2, static_cast<int>(4.5) }; // OK both in C++98 and C++11
Vector2D vec1(4, 5.5); // OK
Vector2D vec2{4, 5.5}; // error - implicit narrowing
Listy inicjalizacyjne¶
Aby umożliwić inicjalizację kontenerów standardowych za pomocą składni z nawiasami klamrowymi C++11 wprowadza nowy typ - std::initializer_list
.
std::vector<int> v{1, 2, 3}; // creates vector with items: [1, 2, 3]
v.insert(v.end(), { 99, 22, 11, 22, -1 });
v = { 1, 2, 3, 5 };
Klasa initializer_list<T>¶
Typ zdefiniowany w pliku
<initializer_list>
plik ten jest dołączany przez inne pliki nagłówkowe (np.
<utility>
)
Przechowuje elementy listy inicjalizującej w tablicy i implementuje ograniczony interfejs umożliwiający dostęp do elementów przy pomocy iteratorów:
size()
- ilość elementów w tablicybegin()
- wskaźnik (const T*
) do pierwszego elementu tablicyend()
- wskaźnik (const T*
) wskazujący koniec zakresu
Elementy listy inicjalizującej są niezmienne - immutable
Jako argument funkcji initializer_list
powinien być przesyłany przez wartość:
#include <initializer_list>
void show_items(std::initializer_list<int> args)
{
for(const auto& item : args)
std::cout << item << "\n";
}
show_items({1, 2, 3, 4, 5, 6, 7 });
Przykład użycia initializer_list
w konstruktorze klasy:
template<typename T>
class Container
{
public:
Container(std::initializer_list<T> items); //initializer-list constructor
//...
private:
int size_;
T* items_;
};
template<typename T>
Container::Container(std::initializer_list<T> items)
: size_{ items.size() } //set container's size
{
reserve(size_); // get the right amount of space
uninitialized_copy(items.begin(), items.end(), items_);
// initialize elements in items_[0:items.size())
}
// ...
Container<int> c1 { 1, 2, 3 }; // OK
Container<double> c2 = { 1.2, 3.14, 5.0 }; // OK
Container<int> c3 { 1, 2, 3.5 }; // error - template parameter T can't be deduced
Listy inicjalizacyjne w przeciążonych konstruktorach¶
Jeśli klasa posiada wiele wersji konstruktora, przy wywołaniu konstruktora poprzez nawiasy klamrowe preferowany jest konstruktor z std::initializer_list
.
class Gadget
{
public:
Gadget(int, int);
Gadget(int, std::string);
Gadget(std::initializer_list<int>);
};
Gadget g1(77, "a"); // calls Gadget::Gadget(int, string)
Gadget g2 {77, "a"}; // calls Gadget::Gadget(int, string)
Gadget g3 = { 77, "a"}; // calls Gadget::Gadget(int, string)
Gadget g5(33, 22); // calls Gadget::Gadget(int, int)
Gadget g6 { 33, 22 }; // calls Gadget::Gadget(initializer_list)
Gadget g7 = { 33, 22 }; // calls Gadget::Gadget(initializer_list)
auto il = { 77, "a"}; // error - no initializer list deductable
auto il = { 77, 3, 4 }; // OK - initializer_list<int> deduced
Gadget g8(il); // calls Gadget::Gadget(initializer_list)
Gadget g9 = {}; // calls Gadget::Gadget(initializer_list)
// or calls default constructor if exists
Ważne
Reguła wywołań konstruktorów:
() - inicjalizacja wywołuje normalne konstruktory
{} - inicjalizacja wywołuje również konstruktory z listą inicjalizacyjną
konstruktory z
std::initializer_list
maję wyższy priorytet
Listy inicjalizacyjne a dedukcja typów¶
std::initializer_list
jest jedynym wyjątkiem różniącym dedukcję typów auto
od
dedukcji typów w szablonach:
auto items = { 1, 2, 4, 5 }; // items is std::initializer_list<int>
template <typename T>
void foo(T param)
{
// ...
}
foo({ 1, 2, 3, 4, 5 }); // error - deduction failed
Słowo kluczowe - decltype¶
Słowo kluczowe decltype
umożliwia kompilatorowi określenie zadeklarowanego typu dla podanego jako argument obiektu lub wyrażenia.
std::map<std::string, float> coll;
decltype(coll) coll2; // coll2 has type of coll
using EntryT = decltype(coll)::value_type; // EntryT is std::pair<const std::string, float>
Jeżeli podajemy jako argument wywołania decltype()
wyrażenie, to nie jest ono ewaluowane.
map<int, std::string> coll;
cout << "sizeof: " << sizeof(decltype(coll[0])) << "\n"; // prints 8
static_assert(std::is_same_v<decltype(coll[0]), string&>)
assert(coll.size() == 0);
Nowa składnia deklaracji funkcji¶
Nowa alternatywna składnia deklaracji funkcji pozwala deklarować typ zwracany po liście parametrów funkcji.
Pozwala to na specyfikację zwracanego typu wewnątrz funkcji oraz z użyciem argumentów funkcji.
int multiply(int a, int b);
auto multiply(int a, int b) -> int;
W połączeniu z decltype
umożliwia specyfikację typu na podstawie wyrażenia wykorzystującego
argumenty funkcji:
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b)
{
return a + b;
}
Automatyczna dedukcja typu zwracanego z funkcji (C++14)¶
Dedukcja z auto¶
W C++14 typ zwracany z funkcji może być automatycznie dedukowany z implementacji funkcji. Mechanizm dedukcji jest taki sam jak mechanizm automatycznej dedukcji typów zmiennych.
auto multiply(int x, int y)
{
return x * y;
}
Jeśli w funkcji występuje wiele instrukcji return
muszą one wszystkie zwracać wartości tego samego typu.
auto get_name(int id)
{
if (id == 1)
return "Gadget"s;
else if (id == 2)
return "SuperGadget"s;
return string("Unknown");
}
Rekurencja dla funkcji z auto
jest możliwa, o ile rekurencyjne wywołanie następuje po przynajmniej jednym wywołaniu return
zwracającego wartość nierekurencyjną.
auto factorial(int n)
{
if (n == 1)
return 1;
return factorial(n-1) * n;
}
Dedukcja z decltype(auto)¶
Deklaracja decltype(auto)
jako typu zwracanego z funkcji powoduje zastosowanie do dedukcji typu mechanizmu decltype
(zachowującego referencje i modyfikatory const
oraz volatile
) zamiast mechanizmu auto
.
template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
Mechanizm decltype
może być również używany do deklaracji zmiennych:
int i;
int&& f();
auto x3a = i; // decltype(x3a) is int
decltype(i) x3d = i; // decltype(x3d) is int
auto x4a = (i); // decltype(x4a) is int
decltype((i)) x4d = (i); // decltype(x4d) is int&
auto x5a = f(); // decltype(x5a) is int
decltype(f()) x5d = f(); // decltype(x5d) is int&&
Gadget g;
const Gadget& cg = g;
auto my_auto1 = cg; // auto type deduction: my_auto1's type is Gadget
decltype(auto) my_auto2 = cg; // decltype type deduction:
// my_auto2's type is const Widget&