C++
This page is a collection of various bits of knowledge related to C++. When I come across something that I find hard to remember, I just note it down here.
Also see the C++ Snippets page on this wiki.
References
- The C++ standard documents: Can be found via this Stack Overflow answer, which contains a continuously updated list of URLs.
No "super" keyword
The lamento
Some programming languages have a keyword super
, or some similar construct, that allows references to the superclass of a subclass. Alas, C++ has no such keyword. The reason is simple: Since C++ supports multiple inheritance, C++ has no way to know which of the possibly several superclasses super
is supposed to refer to.
If there is this inheritance tree "C1 -> C2 -> C3", referring to a superclass implementation of a method looks like this:
void C3::Foo() { C1::Foo(); }
Note that if C2 also implements Foo()
, that implementation is going to be skipped by the above code snippet. The proper way to write this would therefore be:
void C3::Foo() { C2::Foo(); }
If C2 does not implement Foo()
, this example will not trigger a compilation error, instead C1's implementation of Foo()
will be invoked.
A possible workaround
In projects where mulitple inheritance is not used, the clever use of typedef
allows the following workaround:
// A.hpp class A : public B { private: typedef B Super; }; // A.cpp #include "A.hpp" A::A() : Super() { }
Notes
- The typedef is class-specific so it won't pollute the global namespace
- The typedef is private, so it won't affect subclasses
- The approach could be used even in multiple inheritance projects, the only problem is to find (and stick to) a convention which of the several superclasses should be identified by the typedef.
Functions in subclasses hide same-name functions in superclasses
The following code snippet (slightly adapted from the first Stackoverflow question below) results in a compiler error in C::bar()
:
class A { public: void foo(const std::string& s) {}; }; class B : public A { public: int foo(int i) {}; }; class C : public B { public: void bar() { std::string s; foo(s); } };
The explanation can be found in the answers to the following Stackoverflow questions. The error is still weird and unintuitive to me, but apparently according to the standard.
- http://stackoverflow.com/questions/411103/function-with-same-name-but-different-signature-in-derived-class
- http://stackoverflow.com/questions/1484641/c-override-overload-problem
Function pointers and member function pointers
This codeproject.com article is a thorough (some might say "excessive") treatment of function pointers, member function pointers and their use as delegates. Here I only paraphrase the most essential parts in the simplest possible ways; read the article to get the full story.
Example of a function pointer:
float foo(int i, char* s); // the function prototype float (*aFunctionPointer1)(int, char*); // a function pointer variable named "aFunctionPointer1" typedef float (*AFunctionPointerType)(int, char*); // typedef makes usage of the type much more readable AFunctionPointerType aFunctionPointer2; // see? aFunctionPointer2 = foo; // assignment; aFunctionPointer1 works the same as aFunctionPointer2 (*aFunctionPointer2)(42, "bar"); // calling the function
Example of a member function pointer:
class Bar { public: float foo(int i, char* s); // the function prototype }; float (Bar::*aFunctionPointer1)(int, char*); // a function pointer variable named "aFunctionPointer1" float (Bar::*aFunctionPointer1)(int, char *) const; // same, but for a const member function typedef float (Bar::*AFunctionPointerType)(int, char*); // typedef is your friend AFunctionPointerType aFunctionPointer2; aFunctionPointer2 = &Bar::foo; // assignment; aFunctionPointer1 works the same as aFunctionPointer2 aFunctionPointer2 = &Bar::operator !; // the syntax for operators Bar* aHeapObject = new Bar(); // operator ->* is used to call the function for objects on the heap Bar aStackObject; // operator .* is used to call the function for objects on the stack (aHeapObject->*aFunctionPointer2)(42, "bar"); // the operators have low precedence, so parantheses must be placed around them (aStackObject.*aFunctionPointer2)(42, "bar");
Notes:
- The
::*
in the declaration of the function pointer is an operator! - Member function pointers can only point to member functions of a single class, i.e. if another class also has a member function with the same signature, this counts as a different type and a different function pointer must be used! This is reflected by the fact that the function pointer declaration includes the class name.
- Only non-static member functions can be referenced in this way. For static member functions, regular function pointers must be used.
- There is no way to have a function pointer of a constructor or destructor
Stack Unwinding
Stack unwinding also takes place in those functions that do not catch an exception, but that are just passed over as the exception propagates up the stack.
One exception is Microsoft's VC++: There is a compiler option (/EH) that allows to specify the exception handling model. Unfortunately the default model assumes that extern "C"
functions do not throw exceptions, which for those functions causes the compiler to optimize away the code that would take care of stack unwinding.
Passing variable number of arguments to another function
// Calling this function from f3() does NOT work void f1(int iii, char* pFormat, ...) { printf(pFormat, &pFormat + sizeof(pFormat)); } // Calling this function from f3() DOES work void f2(int iii, char* pFormat, va_list pArguments) { vprintf(pFormat, pArguments); } void f3(int iii, char* pFormat, ...) { f1(iii, pFormat, ???); // variable argument list has no name, so no way to call f1() directly // Although slightly complicated, this is the correct way to do it. // The function we are invoking must have a va_list argument. va_list pArguments; va_start(pArguments, pFormat); f3(iii, pFormat, pArguments) ; va_end(pArguments) ; // This also works because the values in the variable argument list are // on the stack in one contiguous memory region. The method above is, // however, much better and clearer. f2(iii, pFormat, &pFormat + sizeof(pFormat)); // Compiles but does NOT work at runtime, because of how f1() is implemented: // Here we pass to f1() merely a POINTER to the variable argument list, // not a COPY of the arguments themselves. But f1() does not know that, and // it in turn passes a copy of the pointer on to printf(). Now printf() starts // interpreting the pointer as the arguments themselves, which at best crashes // the program, or at worst results in weird behaviour. f1(iii, pFormat, &pFormat + sizeof(pFormat)); }
Also see the answer to this this Stackoverflow question.
delete on void* does not work
The following results in a compiler error!
Foo* f = new Foo(); void* v = f; delete v;
Reason: According to the C++ standard, the type that is passed as the operand to delete
must be at least a base class of the object's actual type AND the type passed to delete
must have a destructor marked as virtual
(see the comment by laserlight in forum post).
So not only is it impossible to destroy an arbitrary object using the type void
, it would also not work if we tried to destroy an object by passing a wholly unrelated type to delete
. I have not tried out what the following does, but I assume it compiles and then barfs at runtime:
Foo* f = new Foo(); Bar* b = reinterpret_cast<Bar*>(f); delete b;
On the use of NDEBUG
Who defines NDEBUG?
The preprocessor macro NDEBUG
is used by the standard header assert.h
to detect whether the use of the assert
preprocessor macro should resolve to nothing, or actually do something. Although the C++ standard describes the use of NDEBUG
in relation to assert.h
, it does not say anything about who is responsible for defining the macro.
Consequently, C++ compilers cannot be relied upon to automatically define the macro.
In his 3rd edition of "The C++ Programming Language" Stroustrup says the following about NDEBUG
when he talks about assertions (p. 750):
NDEBUG is usually set by compiler options on a per-compilation-unit basis. [...] Like all macro magic, this use of NDEBUG is too low-level, messy, and error-prone. [...] Using NDEBUG in this way requires that we define NDEBUG with a suitable value whether or not we are debugging. A C++ implementation does not do this for us by default [...]
When to use NDEBUG
One should never use NDEBUG
in any other context than to control the behaviour of assertions, since the C++ standard allows anyone to define/undefine NDEBUG
at will and multiple times, even within the same translation unit.
It is therefore common to use another macro to control a "compile-wide" debug flag. The application programmer could use an application-specific macro MY_DEBUG
. On the MSVC platform, the compiler automatically defines _DEBUG
when the compiler options /MTd
or /MDd
are specified.
Useful references for this topic are this and this Stackoverflow question.
Name mangling / Decoration
Basics
C++ supports function overloading, for this reason a C++ compiler performs so-called "name mangling" or "decoration" when it generates the symbol name for a function. Encoded in the symbol name are the function's name and the function's parameter types. The encoding scheme is entirely compiler dependent, i.e. different compilers will produce different symbol names.
See the GCC page for a method on how to undecorate mangled symbol names produced by GCC.
On Windows the popular and free developer tool Dependency Walker is capable of undecorating symbols in an executable binary.
extern "C"
C does not support function overloading, so a C compiler has no need for name-mangling/decoration. When a C program tries to use a function from a C++ library, the C linker therefore expects to find an undecorated symbol in the library file that it uses for linking. This will fail because the C++ compiler/linker used to generate the library file has generated decorated symbols.
To circumvent this problem, a programmer can use the extern "C"
declaration to specify that the symbol for a certain function should not be decorated. For instance:
extern "C" void foo(int i);
It is also possible to use the declaration on multiple functions/symbols:
extern "C" { void foo(int i); void bar(char c); int i; }
New stuff in modern C++
This section contains some of the new stuff introduced in modern revisions of the C++ standard, starting with C++11, that I personally found useful. The section is not complete!
If not specified otherwise, the new feature was added in C++11.
References:
- C++11
- Wikipedia page on C++11. Quite detailed with examples.
- C++14
- Wikipedia page on C++14. Rather concise.
- C++17
- Wikipedia page on C++17. Mostly a high-level list of additions/changes.
- The article C++ 17 Features from "Bartek's coding blog" is more detailed and contains examples.
- This StackOverflow answer also has a number of useful links.
- A two-part article series on fluentcpp.com also has an overview of C++17 changes/additions, with links to useful detail articles on the same site or elsewhere on the net: Part one and Part two.
New type long long int
Up until now we had long int
, which is guaranteed to have at least as many bits as int
. On some implementations, long int
is 64 bits, on others it is 32 bits.
The new type long long int
is guaranteed to be at least as large as a long int
, and have no fewer than 64 bits. long long int
was already introduced to standard C by C99, so most compilers already support this.
Enumerations
Enumerations finally become first class citizens! Up until now enumerations were merely glorified integer values, but with C++11 enumerations are now, finally, strongly typed. To take advantage we must opt in by using a new syntax. It's as simple as adding the keyword class
(or struct
as a synonym) to the declaration:
// Still has the underlying type "int" enum class Foo { Value1, // still gets implicit value 0 Value42 = 42, Value43 // still gets implicit value 43 };
Even better, we can now also specify an underlying type:
enum class Bar : std::string { Value1 = "one", Value2 = "two", ValueUnknown // TODO: what are the rules for implicit values when underlying type is not "int"? };
More goodness: Value names are now properly scoped within their enum declaration, so no more pollution of the global namespace and conflicting enum value names. We write this:
Foo fooValue = Foo::Value1; Bar barValue = Bar::Value1;
Last, but definitely not least: Enums can now be forward declared. As with any forward declarations, the compiler must know about the size of the forward-declared type, so the underlying type of an enum must be part of the forward declaration:
// Without specification of the underlying type the compiler assumes the default "int" enum class Foo; enum class Bar : std::string;
Null pointer constant
A new constant has been introduced that denotes a null pointer:
MyClass* myClass = nullptr;
The type of the constant is nullptr_t
, which is implicitly convertible and comparable to any pointer type. It is not implicitly convertible or comparable to integral types, except for bool
. Apparently it was decided that making nullptr
behave more like regular pointer types would be less surprising - personally I find this a bit strange.
char* pc = nullptr; // OK int* pi = nullptr; // OK bool b = nullptr; // Weird, but OK. b is false. int i = nullptr; // Error
Finally, some overloading examples:
void foo(MyClass*); void foo(int); void bar(MyClass*); void bar(nullptr_t); foo(0); // calls the int overload foo(nullptr); // calls the MyClass overload bar(nullptr); // calls the nullptr_t overload
Final classes and methods
To make sure that a class cannot be subclassed:
class Foo final {};
To make sure that a virtual method cannot be overridden in a subclass:
virtual void f() final;
Note: final
is not a compiler keyword, it is more like an attribute specifier that gets special meaning only in the specific context of a class/method declaration. It is therefore legal to use "final" as a variable name.
Explicit overrides
Up until now when a subclass wanted to override a virtual method in a base class, it simply did so by repeating the method declaration with the exact same signature as it appeared in the base class:
class Base { virtual void f(int); virtual void g(int); } class Sub { virtual void f(int); // implicitly override base class f() virtual void g(short); // oops! signature is not the same, so no override - but code compiles! }
What happens if in the above example the writer of class Base
decides to change the signature of method f()
? The intended subclass override suddenly stops working! To prevent this it is now possible to explicitly declare that one wants to override a base class method:
class Base { virtual void f(int); virtual void g(int); } class Sub { virtual void f(int) override; // explicitly override base class f() virtual void g(short) override; // signature is not the same - compiler error! }
Right angle bracket
Up until now we annoyingly had to use an artificial space character in declarations that involved even semi-complex template usage:
std::vector<std::pair<int, int> >;
The reason was that without a space the compiler would treat the two consecutive right angle brackets as the ">>" operator, which would result in a compiler error. This is no longer necessary, so we can finally write:
std::vector<std::pair<int, int>>;
Explicit conversion operators
The explicit
keyword can now also be applied to operator declarations, to prevent an operator from being surprisingly used in an implicit conversion.
class Testable { explicit operator bool() const { return false; } }; int main() { Testable a, b; if (a) { /*do something*/ } // OK if (a == b) { /*do something*/ } // compiler error }
Type inference
The new keyword auto
can be used to let the compiler figure out the right type of a variable. This is useful when writing out a complicated type would make the code unreadable. As far as I know, auto
is pretty much the same as var
in C# / .NET.
For instance:
for (std::vector<int>::const_iterator it = aVector.cbegin(); it != aVector.cEnd(); ++it)
can simply become
for (auto it = aVector.cbegin(); it != aVector.cEnd(); ++it)
Related to this is the keyword decltype
, which lets the programmer say "use the same type as this other thing". Examples:
int a; decltype(a) b; // b has type int, i.e. the same type as a auto c = 0; // c has type int decltype(c) d; // d has type int, i.e. the same type as c decltype((c)) e = c; // e has type int&, because (c) is an lvalue decltype(0) f; // f has type int, because 0 is an rvalue
Simpler for loops
The for
loop syntax has been simplified if you want to iterate over some container. The new syntax can be used on
- C-style arrays
- Initializer lists
- Any type that has
begin()
andend()
functions defined for it that return iterators (e.g. all containers in the STL)
This is the new syntax:
int myArray[5] = {1, 2, 3, 4, 5}; for (int& x : myArray) { x *= 2; } // Combine new syntax with type inference / auto keyword for (auto& x : myArray) { x *= 2; }
Constructor chaining
The syntax for constructor chaining (or "delegation", as it's officially called in the C++ terminology) is obvious and follows the syntax for member initialization.
class MyClass { private: int myValue; public: MyClass(int aValue) : myValue(aValue) { } MyClass() : MyClass(42) { } }
The "side-effect" that needs to be considered:
- Object construction is considered to be complete once any constructor is finished. This means that a delegating constructor will operate on a fully constructed object!
- Subclass constructors will execute only after all constructor delegation in the superclass is complete
Member initialization
Member initialization is now possible directly as part of the declaration:
class MyClass { private: int myValue = 42; public: // Will use the member initialization code MyClass() { } // Will ***NOT*** use the member initialization code MyClass(int aValue) : myValue(aValue) { } }
Explicit control over default constructor and copy constructor
class MyClass { // Up until now, specifying a custom constructor prevented the compiler from // generating a default constructor MyClass(int value); // Here we explicitly tell the compiler to generate a default constructor for us MyClass() = default; // Here we can explicitly remove copy constructor and copy operator from legal // use, instead of declaring them with visibility "private" MyClass(const MyClass&) = delete; MyClass & operator=(const MyClass&) = delete; void f(double d); // Without this, someone could call f(42) and the integer would be silently // converted into a double. Here we explicitly disallow such silent conversion, // if someone calls f(42) the result is a compiler error. void f(int) = delete; void g(double d); // Nice use of templates: Disallow calling the function with any type other // than double template<class T> void g(T) = delete; }
Initializer lists
Standard containers from the STL can now be initialized with initializer lists, e.g.
std::vector<int> v = { 1, 2, 3 };
This is possible due to a new STL type
std::initializer_list<>
The STL type can be used in your own constructors, which are then called "initializer-list constructors", a constructor type that is specially treated during uniform initialization:
class MyClass { public: MyClass(std::initializer_list<int> list); void doIt(std::initializer_list<int> list) }; MyClass myClass = {1, 2, 3};
For what it's worth, std::initializer_list<int>
can also be used in regular non-constructor methods:
void doIt(std::initializer_list<int> list); doIt({1, 2, 3});
Uniform initialization
There is a new uniform initialization syntax that uses braces ("{}"):
struct MyStruct { int x; double y; }; class MyClass { MyClass(const std::string& s); MyClass(std::initializer_list<int> list); MyClass(int x); }; MyStruct myStruct{5, 3.2}; MyClass myClass1{"foo"}; MyClass myClass2{42}; // with uniform initialization syntax, the intializer list constructor takes precedence over the constructor that takes an int MyClass myClass2(42); // use classic constructor syntax to access the constructor that takes an int MyStruct getMyStruct() { return {5, 3.2}; }
Direct-list-initialization of enumerations
This feature was added in C++17. It extends uniform initialization to work for enumerations as shown in this example:
enum class Index : uint32_t { // no enumerator }; Index foo = { 42 }; void bar(Index index) { ... } bar( { 42 } );
The example above comes from the original proposal P0138R2 (link to PDF. The idea of the code is that an enumeration without an enumerator can be used to define a new distinct integer type. Because it's a distinct new type (not just a typedef) one would normally have to use static_cast
to assign a value to a variable or parameter of the enum type. Since C++17 it is now possible to assign a value of the underlying type directly using the brace initialization syntax.
Static assertions
Static assertions evaluate constant expressions at compile time:
template<class T> struct Check { static_assert(sizeof(int) <= sizeof(T), "T is not big enough!"); };
Improved sizeof()
This is now valid:
class A {); class B { A myMember; } size_t size = sizeof(B::myMember);
Function pointer declarations
A new, more readable way to declare function pointers:
typedef void (*FunctionType)(double); // old style using FunctionType = void (*)(double); // new style with "using" keyword
New character types, string literals, and Unicode support
These are the basic character types, two of them are new in C++11:
char c1; // intended to store UTF-8 (or any plain old C-style string) char16_t c2; // intended to store UTF-16 char32_t c3; // intended to store UTF-32
Here is an example of the new string literal syntax:
const char[] utf8String1 = u8"An UTF-8 string"; // The number after \u is hexadecimal (without the usual 0x prefix) and represents a 16-bit Unicode code point const char[] utf8String1 = u8"An UTF-8 string with a Unicode character: \u2018"; const char16_t[] utf16String1 = u"An UTF-16 string"; const char16_t[] utf16String2 = u"An UTF-16 string with a Unicode character: \u2018"; const char32_t[] utf32String1 = U"An UTF-32 string"; // The number after \U represents a 32-bit Unicode code point const char32_t[] utf32String1 = U"An UTF-32 string with a Unicode character: \U00002018"; // The literal is enclosed between "( and )" const char[] rawLiteralString1 = R"(No need to escape special characters like \ or " until the end)"; // "delimiter" can be any string up to 16 characters, excluding spaces, control characters, and the characters '(', ')' and '\' const char[] rawLiteralString2 = R"delimiter(Another one with "( and ") character sequences)delimiter"; // Combine Unicode and raw literal prefixes const char[] rawLiteralString3 = u8R"(A raw UTF-8 string)";
Lambda expressions / closures
The MSDN article Lambda Expressions in C++ provides a very clear overview of the syntax of lambda expressions.
C++11 introduces lambda expressions, which are anonymous functions, or unnamed function objects. This is the syntax:
[capture](parameters) mutable-specification exception-specification -> return_type { function_body }
Example:
[](int x, int y) -> int { return x + y; }
The capture clause []
:
- Specifies which variables from outside the lambda expression are captured and how they are made available to the lambda expression.
- All variables that a lambda expression wants to access must be listed in the capture clause, separated by commas. A lambda expression with an empty capture clause accesses no variables.
- Variables that are listed with an ampersand (
&
) prefix are accessed by reference. Variables that do not have an ampersand prefix are accessed by value. - A capture clause can define a default capture mode: Either
[&]
or[=]
. This means that outer-scope variables don't have to be explicitly listed, the lambda expression can simply use them and the compiler will make them available. The default capture mode specifies whether the variables are accessed by reference or by value. - A capture clause can use a default capture mode, and then specify the opposite mode explicitly for specific variables. Example:
[=, &someVariable]
- The lambda expression can access class member variables only if it captures
this
. In C++14this
can be captured by value with a capture clause like this:[*this]
. - In C++14 and newer it is possible to introduce new variables inside the capture clause. The MSDN article discusses this in a section titled "Generalized capture". The
Parameters
- In general this works like a function's declaration of its parameters.
- If the lambda expression has no parameters the entire parameter declaration, including the surrounding paranthesis, can be omitted.
- In C++ 14 the keyword
auto
can be used to make the input parameter type generic. This tells the compiler to create the function call operator as a template.
Notes on mutable-specification
and exception-specification
:
mutable-specification
- Specify nothing: The lambda expression is not allowed to change variables captured by value.
- Specify the
mutable
keyword: The lambda expression is allowed to change variables captured by value. - This has no effect on variables captured by reference
exception-specification
- Specify nothing: The lambda expression is allowed to throw exceptions.
- Specify
noexcept
: The lambda expression is not allowed to throw exceptions.
Return type
- In general this works like a function's declaration of its parameters.
- If the lambda expression has no return value the entire return type declaration, including the preceding "->", can be omitted.
- The return type declaration can also be omitted if the lambda expression has only one return statement, because in this case the compiler deduces the return type from the type of the return expression.
C++14 added that lambda expressions can be assigned to a variable that is declared with the auto
keyword.
auto myLambda = [...]
In order to pass a lambda expression as a parameter to another function a reference to it needs to be stored in an std::function
. See the next section for an example.
std::function
C++11 adds std::function
to the standard library, which is used to store a reference to anything which can be called (free function pointers, member function pointers, or functors).
Here are a few examples, see cppreference.com for more.
void globalPrintInteger(int i) { std::cout << i << std::endl; } class Foo { public: void printInteger(int i) const { std::cout << i << std::endl; } }; typedef std::function<void(int)> CallbackFunction; class Bar { public: void doSomethingWithCallback(CallbackFunction callbackFunction) { callbackFunction(42); } }; Bar bar; // Store a free function std::function<void(int)> f1 = globalPrintInteger; f1(42); bar.doSomethingWithCallback(f1); // Store a lambda std::function<void(int)> f2 = [](int i) { globalPrintInteger(i); }; f2(42); bar.doSomethingWithCallback(f2); std::vector<std::function<void(int)>> callables = { f1, f2 }; for (auto callable : callables) callable(42); // Store a call to a member function std::function<void(const Foo&, int)> f3 = &Foo::printInteger; Foo foo; f3(foo, 42);
std::mem_fn
C++11 adds std::mem_fn
to the standard library, which is one of the most convenient ways that I know of to store a callable reference to a member function.
class Foo { public: void printInteger(int i) const { std::cout << i << std::endl; } }; auto printIt = std::mem_fn(&Foo::printInteger); Foo f; printIt(f, 42);
Multi-threading
The STL now has a number of new types to work with threads:
std::thread std::mutex std::recursive_mutex std::condition_variable std::condition_variable_any std::lock_guard std::unique_lock std::async std::future
In addition there is now a new storage specifier
thread_local
No details available at this time.
Additions to the STL
- Hash tables:
std::unordered_map
,std::unordered_set
and more - Regular expressions:
std::regex
,std::regex_search
,std::regex_replace
andstd::match_results
- Smart pointers:
std::unique_ptr
,std::shared_ptr
andstd::weak_ptr
. Convenient shortcuts to create objects of these types arestd::make_shared
and (since C++14)std::make_unique
. - Tuples:
std::tuple
andstd::make_tuple()
. Values from a tuple can be fetched by index:std::get<3>(myTuple)
. Since C++14 values can also be fetched by type, as long as the tuple does not contain more than one value of the desired type:std::get<int>(myTuple)
. - Random number generator facilities
Various literal improvements
Numeric literals in C++14 can be specified in binary form. The syntax uses the prefixes 0b
or 0B
.
Example:
int foo = 0b10001111;
For better human readability the C++ 14 standard allows using the single-quote character to separate digits in long numeric literals.
Examples:
int foo = 100'000; double bar = 1'000.000'04; int baz = 0b1000'1111;
C++11 defined a syntax for user-defined literal suffixes. The C++14 standard library makes use of this feature by adding a number of such suffixes. For me the most notable is s
for strings. Here are some examples:
auto str = "hello world"s; // auto deduces string auto dur = 60s; // auto deduces chrono::seconds auto z = 1i; // auto deduces complex<double>
Mark an entity as "deprecated"
Since C++14 it is possible to mark an entity with the deprecated
attribute. A compiler should generate a warning when it sees something used that was declared deprecated. An optional string literal can appear as the argument of deprecated, to explain the rationale for deprecation and/or to suggest a replacement.
Since C++17 it's possible to mark enumeration members and namespaces with attributes such as deprecated
.
[[deprecated]] int f(); [[deprecated("g() is thread-unsafe. Use h() instead")]] void g( int& x );
Attributes "fallthrough", "nodiscard", "maybe_unused"
The fallthrough
attribute marks a fallthrough in a switch statement as intentional. This suppresses a compiler warning.
switch (c) { case 'a': f(); // Warning emitted, fallthrough is perhaps a programmer error case 'b': g(); [[fallthrough]]; // Warning suppressed, fallthrough is intentional case 'c': h(); }
The nodiscard
attribute is used to express that a function's or method's return value should not be discarded. A violation generates a compiler warning.
[[nodiscard]] int foo(); foo(); // warning emitted, return value of a nodiscard function is discarded // Apply attribute to a type in order to mark all functions // which return that type as [[nodiscard]] [[nodiscard]] struct Bar{}; Bar foo(); foo(); // warning emitted
The maybe_unused
attribute is used to suppress compiler warnings about entities that are not used.
static void foo1() { ... } // Compilers may warn about this [[maybe_unused]] static void foo2() { ... } // Warning suppressed void bar() { int x = 42; // Compilers may warn about this [[maybe_unused]] int y = 42; // Warning suppressed }
Nested namespace declarations
Since C++17 nested namespace declarations can appear in a more concise form:
namespace X::Y { ... }
instead of
namespace X { namespace Y { ... } }
Parameter packs
TODO: I saw numerous references to parameter packs. Find out what these are.
init statements for if/switch
Since C++17 the if
and switch
statements can now contain variable initialization, just like we're used from the for
statement since the beginning of days.
// value is visible only inside the if and else statements if (auto value = GetValue(); condition(value)) [...] else [...] // value is visible only inside the switch statements switch (auto value = GetValue(); value) { [...] }
Structured bindings
C++17 added structured bindings, which combines the goodness of auto
with the returning of multiple values from a function using a structured type such as std::tuple
.
Example:
std::tuple<char, int, bool> foo() { char a = 'a'; int i = 123; bool b = true; return std::make_tuple(a, i, b); } // Old style before C++17, declaring each variable and using // std::tie() to bind the variables to the tuple's content char a; int i; bool b; std::tie(a, i, b) = mytuple(); // New style with C++17 auto [a, i, b] = foo();
Structured binding works for all compound types, notably structs
, std::pair
and std::tuple
. Some more useful examples:
for (const auto&[key, value] : mymap) { // process entries } struct BoardSize { int Columns; int Rows; }; BoardSize GetBoardSize() { ... } auto [columns, rows] = GetBoardSize();
Template argument deduction
In C++17 and newer, template argument deduction is the ability of templated classes to determine the type of the passed arguments for constructors without explicitly stating the type.
// Old style for constructing an std::pair std::pair<int, double> pair1(2, 4.5); auto pair2 = std::pair<int, double>(2, 4.5) // A number of make...() functions were introduced to the C++ standard // library to write the above in a more concise manner. But for your // own types you need to write your own make...() function. auto pair3 = std::make_pair(2, 4.5); // In C++17 we can now simply write this ... auto pair4 = std::pair(2, 4.5); // ... or even this! std::pair pair5(2, 4.5);
std::optional
A function that has a return value sometimes cannot determine an actual value to return, for instance because some sort of error occurred. It might return a special value that is otherwise never used in order to indicate "no actual return value". This fluentcpp.com article points out why an example such as the following is brittle and awkward, including some of the common alternatives.
int getValue() { // Value -1 can never be an actual result int returnValue = -1; if ( /* some precondition fails */ ) return returnValue; // do calculations return returnValue; } int value = getValue(); if (value == -1) { // do something special } else { // do something normal }
In C++17 the example above can be rewritten like this. While it looks like it's more code, the code is actually much clearer to read (once you know about std::optional
).
std::optional<int> getValue() { if ( /* some precondition fails */ ) return std::nullopt; // Value -1 can now be a valid default value to be returned from getValue() int returnValue = -1; // do calculations return std::make_optional(returnValue); } auto value = getValue(); // Option 1 to evaluate value using value_or(). int valueToWorkWith = value.value_or(-1); // Option 2 to evalue value. If it evaluates to boolean true // it has an actual value. if (value) { // Equivalent to retrieve the actual value int actualValue1 = value.value(); int actualValue2 = *value; // do something normal } else { // do something special }
std::variant
C++17 adds the new standard library type std::variant
, which allows to store a value that can have one of several possible types.
std::variant<int, float, std::string> Foo() { // These are all valid return 42; return 42.0; return "bar"; } const auto variantValue = Foo(); try { if (std::holds_alternative<int>(variantValue)) std::cout << "it's an int: " << std::get<int>(variantValue) << std::endl; else if (std::holds_alternative<float>(variantValue)) std::cout << "it's a float: " << std::get<float>(variantValue) << std::endl; else if (std::holds_alternative<string>(variantValue)) std::cout << "it's a string: " << std::get<std::string>(variantValue) << std::endl; } catch (std::bad_variant_access&) { std::cout << "bad variant access..." << std::endl; } // This can be useful instead of std::holds_alternative(), but it's more brittle int indexOfTypeHeldByVariantValue = variantValue.index(); if (indexOfTypeHeldByVariantValue == 0) std::cout << "it's an int: " << std::get<int>(variantValue) << std::endl; [...] // std::visit is used to invoke a functor with the value that is // actually stored in the variant struct PrintInfo { void operator()(const int& storedValue) const { std::cout << "it's an int" << storedValue << std::endl; } void operator()(const float& storedValue) const { std::cout << "it's a float" << storedValue << std::endl; } void operator()(const string& storedValue) const { std::cout << "it's a string" << storedValue << std::endl; } }; auto PrintVisitorAuto = [](const auto& storedValue) { std::cout << storedValue << std::endl; }; std::visit(PrintVisitorAuto , variantValue); std::visit(PrintInfo{}, variantValue);
Some notes:
- A variant is not permitted to hold references, arrays, or the type
void
- A default-constructed variant holds a value of its first alternative, unless that alternative is not default-constructible (in which case the variant is not default-constructible either).
- Use
std::monostate
to be able to store an empty value in a variant, or to make it default-constructible.
std::any
C++17 added the new type std::any
to the standard library, which can hold any value at all. This is even more generic than std::variant
.
std::any anyValue; anyValue = 42; std::cout << anyValue.type().name() << ": " << std::any_cast<int>(anyValue) << std::endl; try { float f = std::any_cast<float>(anyValue); } catch (const std::bad_any_cast& e) { std::cout << e.what() << std::endl; } // Here anyValue still holds the int value 42, so the boolean // contains true bool hasValue = anyValue.has_value(); // anyValue now no longer holds a value, so the boolean will become false anyValue.reset(); hasValue = anyValue.has_value(); // Access the contained data via pointer anyValue = 42; int* i = std::any_cast<int>(&anyValue);
File system library
C++17 added a new file system library to the standard library, which is based on boost::filesystem.
TODO: Add more. As of 2020 not all parts of the new library are available on all platforms.
Standard Library I/O streams
Summary
Some key points about streams:
- A stream delegates all operations to an intermediate stream buffer.
- The stream buffer is responsible for performing the actual reading/writing operations on behalf of the stream.
- The stream buffer reads from / writes to an input and output sequence. It is said to control those sequences.
- An input or output sequence can be a file, or a string, or something else. The C++ Standard Library defines streams and stream buffers for files and strings only.
- Stream and stream buffer classes are templates that must be parameterized with the character type (
char
orwchar
)
Stream buffers
Interface reference: http://www.cplusplus.com/reference/streambuf/basic_streambuf/
As its name says, a stream buffer's function is to add the capability of buffering read or write operations. Whether or not a given stream buffer implementation actually performs such buffering is another question, but at least the concept is there.
A stream buffer has a set of internal pointers that reference positions of the buffered part of the input/output sequence. Note that the "end" positions refer to a memory location 1 byte behind the end of the buffer array.
beginning current position end (get/put pointer) ------------------------------------------------------------- Input sequence buffer eback gptr egptr Output sequence buffer pbase pptr epptr
Overflow/underflow
- Overflow is the term used to describe the event when an operation wants to write to the stream buffer, but the buffer is full. In technical terms, the current write position (
pptr
) is equal to the end write position (epptr
). - Underflow is the equivalent term for reading: The underflow event occurs when an operation wants to read from the stream buffer, but there is no content to read. In technical terms, the current read position (
gptr
) is equal to the end read position (egptr
).
I found this StackOverflow question extremly helpful when at one point I had to create my own custom I/O stream. The first sentence of the accepted answer is the most important part:
The proper way to create a new stream in C++ is to derive from std::streambuf and to override the underflow() operation for reading and the overflow() and sync() operations for writing.