Thursday, February 2, 2017

C++ tips, 2017 Week 4 (23-Jan - 29-Jan-2017)

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. .front() and .back()

The sequence containers from STL have these two functions for accessing the first or the last element of the container (or an analogue like std::priority_queue::top). They do not return iterator but a reference to the object in the vector.

We've all seen code like this:
std::vector<SomeElement> elements;

// later

SomeElement element;
// some calculations and initializations involving the element
// possibly passing it around by reference
elements.push_back(element);
//or
elements.emplace_back(std::move(element));
This is where .back() comes handy - instead of creating local variable and than inserting into the container sometimes it is better to insert it first and than work directly with the inserted object getting access to it by using back/front/top/etc:
elements.emplace_back();
auto& element = elements.back();

// same calculations and initializations involving the element
// possibly passing it around by reference
This is applicable in the simpler cases and there is no need to involve copy/move semantics.



2. SIMD

SIMD stands for Single Instruction Multiple Data. Hardware vendors found that in computationaly heavy tasks (especially multimedia) where a lot of independent from one another computations are done over small pieces of data (bytes or shorts for example) it is possible to pack the pieces into one instruction and evaluate it altogether instead of processing one byte per instruction while the rest of the space is left unused. This technique is also called vectorization.

Here is a visualization taken from Basics of SIMD Programming from Cell Programming Primer:
So far so good. The problem is that vectorization is not standardized so you have several options - use assembly (which may be different on different hardware), use compiler specifics, use a third party library (which may be tricky to get into production) or have faith in the compiler to auto-vectorize ( MSVC, gcc, clang).

3. std::string_view

string_view is replacement of (char* str, int charCount...) that we usually see in C code. It is basically a pointer to the start of an array of chars (there are also wstring_view, etc) and a number (count) plus useful functionality. It is a non-owning(!) view of a sequence of chars.

The easiest way to describe what it is all about is to see what member functions remove_prefix and remove_suffix do (the example on Wandbox):

#include <iostream>
#include <string>
#include <string_view>

int main()
{     
const std::string str = "abcdefgh";
   std::string_view sv(str);
   std::cout << sv << '\n';

   sv.remove_prefix(2);
   sv.remove_suffix(2);
std::cout << sv << '\n';
   std::cout << str << '\n';
} 

Both functions just shrink the view and do not change anything in the string we are ... viewing. remove_prefix just increments the pointer by two - the view shrinks. Same goes for remove_suffix it just decreases the length of the view by two. Nothing more. (how the internals of string_view are done is implementation specific but many of the implementations I've seen use pointer and a size_t ). It is also designed to be used as STL container (has .begin(), .end(), etc )

Extremely important thing to remember - string_vew is *not* null terminated. Avoid replacing const std::string& with string_vew where you've used std::string::c_str() to pass a null terminated string to some low level functions. std::basic_string::c_str() guarantees null terminated string, string_view does not.

For more info check this article by Marco Arena or the string_view lecture by Marshall Clow from CppCon 2015.

4. deleting functions

This is not about deleting special member functions (copy/move operations, destructor) but for using = delete; with normal functions (including member functions).

By writing:
void foo(int n) = delete;
we state that we forbid using this overload. And the deleted functions participate in overload resolution.
void foo(int n) = delete;
void foo(double dbl);
if we try to invoke foo(10) we will get a compiler error. If void foo(int n) is just missing/not existing the overload with double will be called.

5. 

I was short on time again. This blogging thing proves to be a little harder than expected. Not because I'm noob (I'm working on that, OK?) but mainly because it appeares that I'm much more disorganized than I expected. Finding time for writing and time for tip mining proves to be tricky. Keeping to tight schedule forces me to improve my efficiency and productivity. But surprisingly I'm not stressed out at all about any of this - discovering how much I don`t know,  how much time I waste, how easy distracted I am, etc. Once I saw it clearly it became just another problem to solve. I really expected to be more panicked.