C++11 … 14 … 17 … 20
Views: 5689
For all of you who have been programming C++ since ages, it makes sense to summarize the changes in C++ after the famous STL introduction in 1998, so let’s summarize the features of C++11, C++14, C++17. Later I’ll add C++20. All examples can be compiled.
Deprecation
Throw is Replaced by Noexcept
In C++11, exception declaration with throw
has been deprecated and replaced by noexcept
. Since C++17, throw
declarations are deprecated considered as error, e.g. «error: ISO C++1z does not allow dynamic exception specifications
»:
throw()
is replaced bynoexcept
ornoexcept(true)
throw(std::exception)
is replaced bynoexcept(false)
My usage recommendation:
- If a class may throw an exception, don’t specify anything. If nothing is declared, you always must expect an exception and do exception handling. This has always been the case, now it’s clear.
- If a class does not throw any exception, declare this with
noexcept
. So only ifnoexcept
is declared, exception handling, i.e. a try-catch-block, is not needed.
Use grep
and sed
to change an entire project:
EXCLUDE='--exclude=*~ --exclude-dir=.cvs --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.boar' /bin/grep ${EXCLUDE} -rl 'throw()' \ | xargs -i{} sed -i 's,throw(),noexcept,g' {} /bin/egrep ${EXCLUDE} -rl 'throw\([^()]+\)' \ | xargs -i{} sed -i 's,throw([^()]\+) *,,g' {}
In addition and in the logical consequence, the unexpected
handler is also deprecated.
Language Enhancements
Null Pointer
There exists keyword nullptr
as null pointer, instead of 0
(NULL
was always discouraged).
Type Calculation and Automatic Detection
If the type of a variable can be determined by the compiler from the initialization value, you can simply specify it as auto
. Auto always generates non-reference-types otherwise use e.g. decltype(auto)
or auto&
.
std::string s("hello world"); auto it(*s.begin()); // it is the first char
Using decltype
, the type can be calculated from an expression. Other than auto
, decltype
can deduce reference types. It can be combined to decltype(auto)
.
int i; decltype(i) j1 = i; // takes a copy of i decltype((i)) j2 = (i); // takes reference &i
In C++11, a calculated function return type must be declared with -> decltype(…)
.
template<typename T1, typename T2> auto mult(T1 a, T2 b) -> decltype(a*b) { return a*b; } … unsigned long b(232); long double a(-1.000100010001); decltype(a*b) c = mult(a, b);
Since C++14, the return type of functions can be detected automatically and decltype
is no more necessary.
template<typename T1, typename T2> auto mult(T1 a, T2 b) { return a*b; }
Structured Binding
#include <iostream> #include <tuple> int main(int, char const *const *const) { // «old» C++11-style char a(' '); int b(0); bool c(false); std::tie(a, b, c) = std::make_tuple('a', 123, true); std::cout<<"a="<<a<<", b="<<b<<", c="<<(c?"true":"false")<<"\n"; // new C++17-style (g++ requires option -std=c++1z) auto [d, e, f] = std::make_tuple('x', 432, false); std::cout<<"d="<<d<<", e="<<e<<", f="<<(f?"true":"false")<<"\n"; return 0; }
Constant Expression
Constant expressions are now more flexible, i.e. a constant expression can be marked explicitly with constexpr
keyword.
constexpr double pi = 3.1415926535897932384626433;
Static Assertion
Template parameters can now be tested with static_assert
. The error message is optional since C++17.
static_assert(constant-expression, error-message);
Alignment
You can specify and query alignment with the new keywords alignas
and alignof
.
alignas(float) unsigned char c[sizeof(float)]
Inline Variables
Variables can be specified
inline
since C++17.
External Templates
Templates can now be declared extern
, so that they are not instantiated in a specific translation unit.
Loops on Container
For loops on container can be specified as:
for (iterator-type loop-variable: container-variable) {statements}
To iterate over a string:
std::string s("Hello World"); for (auto const &c: s) { std::cout<<"character: "<<c<<"\n"; }
With compound collections, such as maps you can use structured binding:
std::map<std::string, std::string> map = {{"hello", "world"}, {"foo", "bar"}}; for (auto&[key, value]: map) { std::cout<<"element: "<<key<<" = "<<value<<"\n"; }
Initialisation in if
and case
The structures if
and case
now may have an initialization section, similar to for
.
if (int i=1; b<1) … switch (auto[a,b]=fn(); a) {…}
Template Deduction
Now template parameters can be deduced automatically.
std::pair p(5.0, 'x');
Brace-Initialisation
You can initialize everything using curly-braces:
struct X { int a; int b; X(): a{0}, b{0} {} X(int _a, int _b): a(_a), b(_b) {} }; X x{4, 5}; std::vector<std::string> v{"hello", "world", "foo", "bar"}; int* a = new int[3] {1, 2, 3}
Typesafe Enums
Enumerations are now strongly typed and it is possible to specify any integer type specifier. Adding keyword class
or struct
creates the constants for the values within the namespace of the enumeration.
enum X: short {A = 15, B = 30}; // use as X a(A); enum class Y: short {A = 15, B = 30}; // use as Y a(Y::A);
Neested Namespaces
Write namespace X::Y { … }
instead of namespace X { namespace X { … } }
.
Non-Trivial Unions
Unions may now contain no-trivial objects, if the union has a correspondig constructor and assignment-operator.
String Constants and Literals
Declare UTF-8 string constants (and since C++17 also single char
s) as u8"??"
in char
or UTF-16 string constants as u"?????"
in char16_t
or UTF-32 as U"?????"
in char32_t
. And there exist raw strings with arbitrary delimiter no longer than 16 characters, such as R"arbDelim(this "is" raw)
, which can be combined, e.g. arbDelim
"u8R"(my raw string)"
.
There is now a 0b
or 0B
literal for binary numbers, such as 0b
101000110
. Numbers can be divided with '
, such as 1'000'000
.
Use literal s for std::basic_string, such as «hello»s.
Use literals h
, min
, s
, ms
, us
, ns
for time in std::chrono::duration
. This requires using namespace std::literals;
.
Complex numbers can be written with i
, if
or il
for std::complex<double>
, std::complex<float>
and std::complex<long double>
.
Hexadecimal literals exist also as float.
Own Literals
Overwriting operator «» you can define your own literals.
#include <iostream> #include <string> #include <cctype> #include <algorithm> std::string operator "" _U(char const *literal, size_t len) { std::string res(literal, len); std::transform(res.begin(), res.end(), res.begin(), toupper); return res; } int main(int, char const *const *const) { std::string name = "Marc"_U; std::cout<<"name="<<name<<std::endl; return 0; }
Template Typedefs
Finally, template typedefs are possible with keyword using
.
template<typename A, typename B, typename C> class X …; template<typename B> using Y = X<int, B, float>;
Template Variables
Since C++14, variables can be templated too.
#include <iostream> #include <iomanip> template<typename T> T pi = T(3.141592653589793238462643383); template<> const char* pi<const char*> = "3.141592653589793238462643383 -> PI"; int main(int, char const *const *const) { std::cout<<std::setprecision(40)<<pi<const char*><<"\n" <<pi<float><<"\n"<<pi<double><<"\n"<<pi<long double><<"\n"; return 0; }
Check Include File Availability
Use __has_include
to check for the availability of include files.
Lambda Expression
A lambda expression is an unnamed inline function, which is especially useful within algorithms. It is used as:
[used external variables, variable = value](parameter){function body}
auto lambda = [value = 1] {return 2*value;};
std::unique_ptr<int> ptr(new int(10)); auto lambda = [value = std::move(ptr)] {return *value;};
In old C++ versions, you would have to define a separate function. As an example, let’s count the non-space characters in a string and create a copy of all characters in the string, that are not white spaces. Without lambdafunctions, this could be:
#include <iostream> #include <string> #include <cctype> bool appendIfNotSpace(unsigned char const c, std::string& s) { if (std::isspace(c)) return false; // don't count s += c; return true; } int main(int, char const *const *const) { std::string s("Hello World,\nthis is\ta text."); std::string textWithoutSpaces; // fill in text without spaces unsigned int cnt(0); for (auto c: s) { if (appendIfNotSpace(c, textWithoutSpaces)) ++cnt; } std::cout<<"«"<<s<<"»\ncontains the following "<<cnt<<" non-space-characters: " <<textWithoutSpaces<<"\n"; return 0; }
With lambda-functions, this becomes much simpler, we can use the function std::count_if
and pass textWithoutSpaces
as additional external reference:
#include <iostream> #include <string> #include <cctype> #include <algorithm> int main(int, char const *const *const) { std::string s("Hello World,\nthis is\ta text."); std::string textWithoutSpaces; // fill in text without spaces unsigned int cnt = std::count_if(s.begin(), s.end(), [&textWithoutSpaces](auto c) { if (std::isspace(c)) return false; // don't count textWithoutSpaces += c; return true; }); std::cout<<"«"<<s<<"»\ncontains the following "<<cnt<<" non-space-characters: " <<textWithoutSpaces<<"\n"; return 0; }
Bind Functions and Reference Wrapper
Use std::bind
to create a function object and use std::placeholders::_1
, std::placeholders::_2, … as placeholder to bind the first parameter, the second , and so on, to the function. Use std::ref
to pass an argument by reference.
#include <iostream> #include <functional> void test(int a, int b) { std::cout<<"a="<<a<<"; b="<<b<<"\n"; } int main(int, char const *const *const) { auto fn1a = std::bind(test, std::placeholders::_1, 5); auto fn1b = std::bind(test, 5, std::placeholders::_1); auto fn2 = std::bind(test, std::placeholders::_1, std::placeholders::_2); fn1a(4); fn1b(2); fn2(8, 9); return 0; }
R-Value References
R-value references can bind to r-values, such as temporary objects or literals. They are especially useful to implement a move-semantic, when a resource is moved from one object to another, leaving the original object empty. A class can have a move constructor and a move assignment to take ownership of temporary ressources:
#include <iostream> #include <iterator> struct X { int *array = nullptr; int size = 0; X(int sz, int* a): size(sz), array(a) { std::cout<<"array\n"; } X(X const &o): size(o.size), array(new int[o.size]) { std::cout<<"copy\n"; std::copy(o.array, o.array+size, array); } X(X&& o): size(o.size), array(o.array) { std::cout<<"move\n"; o.array = nullptr; o.size = 0; } ~X() { std::cout<<"delete "<<size<<" elements from "<<array<<"\n"; if (array) delete array; } void print() { std::copy(array, array+size, std::ostream_iterator<int>(std::cout, ", ")); std::cout<<std::endl; } }; // dummy copy function // to prevent compiler optimization // this enforces move semantics X cp(X x) { return x; } int main(int, char const *const *const) { X x{5, new int[5]{1, 2, 3, 4 ,5}}; x.print(); X x1{x}; // copy constructor x1.print(); X x2{cp(X{4, new int[4]{0, 2, 4, 8}})}; // move x2.print(); return 0; }
Variadic Templates
You find a nice example for variadic templates in C++11 Variadic Templates: Fun with Tuple.
#include <iostream> template <typename... T> struct X { }; template <typename Value, typename... T> struct X<Value, T...> { Value value = Value(); X<T...> next; X(Value v, T... args): value(v), next(args...) {} }; int main(int, char const *const *const) { X<int, float, int> x1(1, 2.5, 4); std::cout<<x1.value<<", "<<x1.next.value<<", "<<x1.next.next.value<<"\n"; return 0; }
Attributes and Deprecation
You can specify attributes. Predefined, there’s [[deprecated]]
and since C++17 [[fallthrough]]
used in case
without break
, [[maybe_unused]]
for unused function parameters, [[noreturn]]
if a function throws an exception before it returns, [[carries_dependency]]
and [[nodiscard]]
.
[[deprecated("replaced by y()")]] void x();
Enhancements for Classes
In-Class-Initialisation
Variables can now be initialized directly in the class declaration:
struct X { int a{0}; int b = 1; };
Deleted and Defaulted Functions
Functions can be declared to be deleted or to be implemented using the default implementation:
struct X { X() = default; // generates default constructor X(X const&) = delete; // prohibits copy constructor };
Calling Constructors
Since C++11, a constructor can call another constructor:
struct X { X() {std::cout<<"X()\n";} X(int): X() {std::cout<<"X(int)\n";} };
Inherited Constructors
All constructors of a base class can be inherited with using
.
struct X { X(int) {} }; struct Y: X { using X::X; // inherit all constructors };
Explicit Overwrite and Final Implementation
If you overwrite a derived method and use keyword overwrite
, then the signature is checked by the compiler. If the signature is not available in the parent class, an error is generated, rather than just creating a new method.
Add keyword final
to a class to prohibit further inheritance or to a method to forbid overwriting it in derived classes.
Explicit Conversion
The keyword explicit
can now be used in conversion operators. This way, a bool-operator cannot be used in an non-boolean context.
struct X { explicit operator bool(); };
Library Enhancements
Function Object
There is a new std::function
class that represents any kind of function or functor. This way, a collection can store any kind of callback functions. Example:
#include <iostream> #include <functional> #include <unordered_map> bool test(int a, int b) { return a<b; } struct functor { bool operator()(int a, int b) { return a>b; } }; int main(int, char const *const *const) { functor fn; // just an instance of the functor // store some functions in a vector (use the new initializer) std::unordered_map<std::string, std::function<bool(int,int)>> v = { {"smaller", test}, {"bigger1", fn}, {"bigger2", functor()}, // another instance of functor {"equal", [](auto a, auto b){return a==b;}} // lambda function }; // generate some combinations of numbers for (int a(0); a<3; ++a) for (int b(0); b<3; ++b) { std::cout<<"---------------------------------\n"; // now, apply all functions to the a/b-combination for (auto&[name,function]: v) std::cout<<name<<"("<<a<<","<<b<<") = " <<(function(a,b)?"true":"false")<<"\n"; } return 0; }
Regular Expressions
You can define regular expressions in std::regex
. Use std::regex_match
to check if a whole string matches the expression, or use std::regex_search
to find the first ocurance of a match within a string. To get all matches from a string, use a token iterator. Example:
#include <iostream> #include <string> #include <regex> int main(int, char const *const *const) { std::string s("string to match, e.g. number 13,8421 or -421, other text"); std::regex number("(\\+|-)?[[:digit:]]+(,[[:digit:]]+)?"); std::smatch match; if (std::regex_search(s, match, number)) { // only first match is found for (auto m: match) std::cout<<"expression: "<<m<<"\n"; // sub expressions // to iterate over all numbers in the string, use a token iterator for (std::sregex_token_iterator it(s.begin(), s.end(), number); it!=std::sregex_token_iterator(); ++it) std::cout<<"found number: "<<*it<<"\n"; } return 0; }
Threads
Finally C++ can do native threads, just pass a function object to std::thread, then detach or join it. Compile with option -pthread
. Lock access using std::lock_guard<std::mutex>
. Use std::packaged_task
together with std::future
to get the return result of a thread. Use std::promise
with std::future
and std::condition_variable
to send signals between threads.
#include <iostream> #include <thread> #include <mutex> #include <future> #include <functional> using namespace std::literals; // to use literal ms for milliseconds std::mutex cout; // mutex to lock access to std::cout struct X { std::string name; void operator()() { for (int i(0); i<10; ++i) { std::this_thread::sleep_for(10ms); // same as std::chrono::milliseconds std::lock_guard<std::mutex> write_cout(cout); std::cout<<name<<':'<<i<<' '<<std::flush; } } }; unsigned long factorial(unsigned long n) { if (n==0) return 1; return n*factorial(n-1); } int main(int, char const *const *const) { std::promise<void> exit; // signal without value std::packaged_task<unsigned long()> task(std::bind(factorial, 20)); // async call factorial(20) std::future<unsigned long> f1 = task.get_future(); // future result of task std::thread t1(X{"one"}), t2(X{"two"}), t3(X{"three"}), t(std::move(task)), t4([&exit](){ unsigned long i{0}; auto future(exit.get_future()); while(future.wait_for(5ms) // same as std::chrono::milliseconds(5) == std::future_status::timeout) { std::lock_guard<std::mutex> write_cout(cout); std::cout<<"tick:"<<++i<<' '<<std::flush; } }); t1.detach(); // one t2.detach(); // two t3.join(); // three t.join(); // factorial calculation task f1.wait(); // wait for the task result exit.set_value(); // trigger exit future t4.join(); // lambda ticks { std::lock_guard<std::mutex> write_cout(cout); std::cout<<"\nresult of task: "<<f1.get()<<"\n"; // not guaranteed at the end } return 0; }
New Container Classes
There are new unordered container-classes: std::unordered_set
, std::unordered_map
, std::unordered_multiset
, and std::unordered_multimap
. The content of the containers is not sorted, but organized in buckets refernced by hashes. This allows faster access.
A tuple
can hold a given number of values of a given type, e.g.:
std::tuple<int, std::string, char, float> t{1, "hello", 'x', 3.14159};
New Shared Pointer
std::unique_ptr
is a smart pointer with unique object ownership. It replaces std::auto_ptr
. std::shared_ptr
is a smart pointer with shared object ownership. std::weak_ptr
holds a thread safe temporary (weak) reference to a std::shared_ptr
.
std::make_unique
does for std::unique_ptr
what std::make_shared
does for std::shared_ptr
.
New Algorithms
Check if std::all_of
, std::none_of
or std::any_of
the values in a container match a criteria. Test if any character is uppercase:
std::string s{"a text with an Upper case character"}; if (std::any_of(s.begin(), s.end(), [](char c){return std::isupper(c);})) …
Copy a number of elements with std::copy_n
. std::iota
creates a range of sequentially increasing values.
Random Numbers
Library random
provides three random number generators and many distributions.
Filesystem
Library filesystem
implements filesystem functionality.
Iterators
In addition to std::begin
and std::end
, there is now also std::cbegin
and std::cend
for constant iterators, std::rbegin
and std::rend
for reverse iterators and std::crbegin
and std::crend
for the combination of constant and reverse.
std::equal
, std::mismatch
or std::is_permutation
check iterators and intervals.
Traits
The new library type_traits
allows static checks, such as std::is_integral<T1>::value
to implement constraints or optimize template implementations. std::is_final
checks whether a class is final.
There are logical operator traits std::conjunction
, std::disjunction
and std::negation
.
Quoted Streams
Manipulator std::quoted
handles quoted streams.
Exchange Values
Exchange values with std::exchange
.
Special Types
std::string_view
is a read-only non-owning reference to a character sequence. std::byte
holds a byte.
std::optional
represents optional variables, std::any
may hold any type, std::variant
is a tagged union container.
Uniform Container Access
Uniform container access with std::size
, std::empty
, std::data
.