Thursday, December 29, 2016

C++ tips, 2016 Week 51 (19-Dec - 25-Dec-2016)

This is part of my weekly C++ posts based on the daily C++ tips I make at my work. I strongly recommend this practice. If you dont have it in your company start it. 
List of all weekly posts can be found here.


1. The first rule about performance
Never guess about performance!
Optimization and performance in general are always tied to a metric. You always optimize something measurable like CPU usage, memory usage, CPU cache utilization, latency, throughput, jitter, power consumption, etc. It does not make sense to talk about performance or optimizations without clarifying on which metric you are optimizing. 

And it is pointless to start optimizing without measuring first. The usual cycle is:

Measure , implement optimization, measure again, compare measurements (prove that it is in fact an improvement), repeat.

It is the scientific method actually. The tricky thing to do is to set up your experiments in a way that the measurements make sense.

2. Attributes

Attributes were introduced in C++11 and provide unified syntax for implementation-defined language extension (ideally replacing __declspec() and __attribute__((...)) in the future). They are designed to be put almost everywhere although most of them are location-specific (like [[likely(true)]] in front of if). From C++17 all unknown to the compiler attributes are ignored without causing an error.

There are few attributes defined by the C++ standard:

[[noreturn]] - indicates that the function will not return (obviously). Usually a function that trows at the end or worse. There are several standard functions that have this attribute - std::exit, std::abort, std::terminate, std::rethrow_exception and several more.

[[carries_dependency]]  - here I hit the limits of my knowledge. I have to read C++ Concurrency in Action by Anthony Williams someday - I hear there are tons of arcane knowledge in it. To avoid saying something stupid I'll just paste from cppreference.com - "Indicates that dependency chain in release-consume std::memory_order propagates in and out of the function, which allows the compiler to skip unnecessary memory fence instructions."

[[deprecated]] and [[deprecated("reason")]] - from C++14, used to issue a warning that something is deprecated - it is allowed to be used but soon may be deleted.

And three more coming in C++17:

[[fallthrough]] - used in switch statements to inform the compiler that we intentionally did not write  break so it will not troll us with a warning.

[[nodiscard]] - this is used to tell us that when a function returns something by value and we should not ignore it. The notorious example being std::async blocking because it returns a std::future by value and when we do not assign it to a variable the temporary std::future calls its destructor immediately where it blocks until it completes the job. You can suppress this warning by explicitly casting to void ( (void) func(); ).

[[maybe_unused]] - this one suppresses warnings about variables not being used. The usual case is something like this:
bool active = !isDestroyed() && !isOnFocus();
ASSERT(active);
this will probably compile without warnings  in debug but in release active is not used so it will issue a warning.
[[maybe_unused]] bool active = !isDestroyed() && !isOnFocus();
ASSERT(active);
 OK now.


You can find more on attributes on Arne Mertz's blog - Modern C++ Features: Attributes

3. alignof/alignas

Each object has an alignment requirement property "which is an integer value (of type std::size_t, always a power of 2) representing the number of bytes between successive addresses at which objects of this type can be allocated."

Modern computer architectures access the memory in word sized chunks (for example four 8-bit bytes on a 32-bit system). So if your data object is for example 13 bytes and you've put them in memory one after another. The second one will start at address 13 and this will force the machine to do one additional memory access to fetch all the data. When we align them the second object will start at address 16 thus nicely fitting into what better suits the hardware. Usually this is done automatically by the compiler by filling the extra space with empty/uninitialized bytes in what is called padding. A more-memory-for-better-CPU-usage trade off.

C++ provides several alignment tools and I will mention only alignof and alignas

alignof - return the alignment of a type-id.

alignas - specifies the alignment requirement of a type or an object. It can be applied for a variable declaration (excluding bitfield data members) or to the to the declaration or definition of a class/struct/union or enumeration:

struct alignas(16) Test{ char a; alignas(double) char b;}; alignas(32) Test test;
4. std::optional 

std::optional<T> is a class template that may or may not contain value. Its use case is when we want to get a value from somewhere but the process can fail. So instead of using std::pair<T, bool> or if (-1 == retValue) return FAIL; (encoding the failure to acquire value in the value itself) we can use std::optional.

You access the value by calling ... the value member function (throws if no value) or value_or. It is contextually convertable to bool so you can use it in if statements:
if (std::optional<int> optionalInt = acquireInt(); optionalInt)
{
    // use the optional in some calculations
}
else
{
    // handle failure to acquire the Int
}
but I personally prefer the has_value member function instead for better readability.

As expected it has all the modern features like emplace, swap, reset, comparison operators and make_optional.

5. fluent interface

Fluent interface is a technique for improving readability where your setter member functions return reference to the called object. This allows to chain them.

For example:
struct S
{
   S& setA(int a) { a_ = a; return *this;}
   S& setB(int b) { b_ = b; return *this;}

   int doSomeCalculations() { return a_ * b_; }
private:
   int a_;
   int b_;
};

int main()
{
   S s;
   auto result = s.setA(4).setB(5).doSomeCalculations();
}

And that's it for this year. I'll skip the last week because laziness holidays! Have a great 2017 everyone!