Modern C++ migration
Welcome to the
std::promised
land
Auto
Type inference is a game changer. Essentially you can simplify complicated (or unknown) type declarations with auto
. But it can be a balance of convenience
over readability.
int x1 = 5; // Explicit
auto x2 = 5; // What's the underlying type?
std::vector<std::string> moon = {"Don't", "look", "at", "the", "finger"};
auto finger = moon.front();
And there are a few perfectly valid gotchas. Let's create a variable and a
reference to it, updating y2
(below) also updates y1
as expected.
int y1 = 1;
int &y2 = y1;
y2 = 2;
But how does auto
deal with references? Do you get another reference or a
copy? (Hint: auto "decays" to the base type -- no consts, no refs).
int y1 = 1;
int &y2 = y1;
auto y3 = y2;
auto &y4 = y2;
Brace initialisers
These take a bit of getting used to but they do give you extra checks. For example the compiler coughs a "narrowing" warning for the following.
double wide{1.0}; float narrow{wide};
Initialiser lists
We used to create a vector and then push elements onto it (ignoring the potential copy overhead of resizing vectors). But with initialiser lists you can populate containers much more concisely.
Initialise a container.
std::list v1{1, 2, 3, 4, 5, 6};
Initialise a pair. You can also use them instead of std::make_pair
.
std::list v1{1, 2, 3, 4, 5, 6};
std::pair<int, std::string> p1{1, "two"};
Initialise more complex types.
std::list v1{1, 2, 3, 4, 5, 6};
struct S {
int x;
struct Foo {
int i;
int j;
int a[3];
} b;
};
S s1 = {1, {2, 3, {4, 5, 6}}};
Range-based for loops
Clumsy explicit iterator declarations can be cleaned up with auto
.
for (std::list::iterator i = v2.begin(); i != v2.end(); ++i)
*i += 1;
for (auto i = v2.begin(); i != v2.end(); ++i)
*i += 1;
In fact we can drop the iterators altogether and avoid that *i
dereferencing
idiom.
for (auto &i : v4)
i += 1;
Note that you don't have access to the current index (until C++20). Which isn't necessarily a bad thing.
Lambda expressions
Think function pointers but a much friendlier implementation. Call like a
regular function or pass them as a parameter. You can also define them in-place
so you don't have to go hunting for the implementation like you might if you
passed a function name. Here's another new for-loop variation too. Note the use
of std::cbegin()
rather than the method.
const auto printer = []{ std::cout << "I am a first-class citizenn"; return; };
// Call like a function
printer();
// In-place lambda definition
const std::vector d{0.0, 0.1, 0.2};
std::for_each(std::cbegin(d), std::cend(d),
[](const auto &i) { std::cout << i << "n"; });
Threads
STL threads are much neater than the old POSIX library but futures are really interesting and let you return the stuff you're interested in much more easily.
Let's define a processor-heavy routine as a lambda. Here the return has been declared explicitly.
const auto complicated = []() -> int { return 1; };
And then push our complicated routine into the background and get on with
something else. Note we don't need to define what f
is thanks to auto
.
(It's actually a std::future
.)
auto f = std::async(std::launch::async, complicated);
When we're ready, we block to get the value. We could change the return type of
complicated()
and nothing else needs to change.
std::cout << f.get() << "n";
Optional types
This overcomes the problem of defining a "not initialised" value (-1) which will inevitably used to index an array and cause an explosion. Your functions can now effectively return a "no result". Let's create a container with some default entries.
std::deque<std::optional> options{0, 1, 2, 3, 4};
Then make the one at the back undefined.
options.back() = {};
And count the valid entries with the help of a lambda expression.
const auto c = std::count_if(std::cbegin(options), std::cend(options),
[](const auto &o) { return o; });
Digit separators
If you're defining hardware interfaces then you'll probably have register maps defined as hexadecimals. Using digit separators can help improve readability in some cases.
int reg1 = 0x5692a5b6;
int reg2 = 0x5692'a5b6;
double reg3 = 1'000.000'001;
You can even define things in binary if it's clearer. And also specify the size of a type explicitly – a 32-bit integer, say – rather than letting the compiler decide.
const uint32_t netmask{0b11111111'11111111'11111111'00000000};
Type aliases
Create type-safe typedefs with using. Note the trailing cluster of angle-brackets are parsed correctly in C++11 (no need to insert spaces).
using container_t = std::vector
Raw strings
Avoid clumsy escape characters with raw strings.
const std::string regex{R"(
^ # start of string
( # first group start
(?:
(?:[^?+*{}()[\]\\|]+ # literals and ^, $
| \\. # escaped characters
| \[ (?: \^?\\. | \^[^\\] | [^\\^] ) # character classes
(?: [^\]\\]+ | \\. )* \]
| \( (?:\?[:=!]|\?<[=!]|\?>)? (?1)?? \) # parenthesis, with recursive content
| \(\? (?:R|[+-]?\d+) \) # recursive matching
)
(?: (?:[?+*]|\{\d+(?:,\d*)?\}) [?+]? )? # quantifiers
| \| # alternative
)* # repeat content
) # end first group
$
)"};
Structured bindings
You might declare intermediate variables to make the first/second more meaningful below.
std::pair<std::string, std::string> chuckle{"to me", "to you"};
std::cout << chuckle.first << ", " << chuckle.second << "n";
But you can also do it in one expression with structured bindings.
auto [barry, paul] = chuckle;
std::cout << barry << ", " << paul << "n";
const everything
Not a modern feature of course, but: make everything constant. You should
be prefixing const
as a matter of course and then removing it when you have
to: it’s much easier to reason about code when the data are immutable. In an
ideal world everything would be constant -- like
Haskell -- but
it’s a balance of reason and getting things done.
Standard literals
using namespace std::complex_literals;
using namespace std::string_literals;
using namespace std::chrono_literals;
// auto deduces complex
auto z = 1i;
// auto deduces string
auto str = "hello world"s;
// auto deduces chrono::seconds
auto dur = 60s;
// Or if you just want them all
using namespace std::literals;
Tuples
Like pairs but better. Arbitrary collection of heterogeneous types. You can retrieve values by index (which looks a bit odd) or even by type!
std::tuple<std::string, double, int> h1{"one", 2.0, 3};
std::string << " " << std::get<0> << " " << std::get<1> << "n";
std::byte
When you really want something to be a byte and not something that looks a bit like a char.
std::byte b1{4};
Exchanging values
Replace that old "declare a temporary variable" idiom with an atomic update.
std::exchange
also returns the original value.
int current = 5;
int previous = std::exchange(current, 6);
Inline keyword
Just let the compiler decide what should be inlined. It will probably ignore you anyway.
Move semantics
This is a biggie that you exploit just by moving to C++11 and beyond. The compiler can now choose to move data where previously it would have copied it, potentially giving huge performance benefits.
Smart pointers
You no longer need to use new and delete explicitly. Smart pointers clean up after themselves when they go out of scope: Resource Allocation Is Initialistion (RAII).
RAII
Used by:
std::vector
std::string
std::thread
The STL also offers wrappers to manager RAII:
std::unique_ptr and std::shared_ptr to manage dynamically-allocated memory or, with a user-provided deleter, any resource represented by a plain pointer; std::lock_guard, std::unique_lock, std::shared_lock to manage mutexes.
See CPP Reference.
References
- https://hackingcpp.com/cpp/cheat_sheets.html
- https://www.stroustrup.com/bs_faq.html
- https://github.com/isocpp/CppCoreGuidelines
- Modern C++ features
- Guidelines by Stroustrup and Sutter
- Language feature support by compiler
- https://github.com/ericniebler/range-v3
- Which C++ features are coming
- https://isocpp.org/
- https://herbsutter.com/
- https://channel9.msdn.com/Shows/Going+Deep/Herb-Sutter-C-Questions-and-Answers#time=0h18m20s
- https://leanpub.com/bookstore?search=c%2B%2B&type=all&category=c_and_cpp#BookstoreTop
- https://www.cppstories.com/
- https://devblogs.microsoft.com/oldnewthing/author/oldnewthing