A handful of C++ idioms

Just like in natural (spoken) languages, C++ coders will probably use idioms from time to time. These are code patterns that perform a specific task, whose function may not be obvious to new C++ coders. Once learnt, however, they can be applied in a variety of settings; a few useful ones are listed here:

1 – Shrink-to-fit

Used with std::string and std::vector mostly, the aim is to reduce memory footprint in a running program. It may be thought that a construct such as s.reserve(s.size()) would to this, however member function reserve() only takes action when increasing the amount of memory reserved for the container. The correct shrink-to-fit construct for a std::string object named s would be:

std::string{ s }.swap(s);

Similarly for a std::vector object named v (as the element type can be deduced in Modern C++):

std::vector{ v }.swap(v);

This idiom works by creating a temporary variable with the contents of the object to be shrunk, and then swaps this temporary with the initial object (the initial object then being discarded immediately and the heap memory it was using reclaimed). A variant of this idiom swaps with an empty object, typically used after a call to member function clear().

2 – Erase-remove

The names of the standard algorithms remove and remove_if are slightly misleading; they never remove anything! Instead they operate by swapping all matching elements to the end of the container. A single call to container method erase then shrinks the container to the correct size, leaving only the desired elements; this is preferred to having multiple calls to erase in combination with find/find_if. (To really release memory, a shrink-to-fit may be necessary, too.)

Both remove and remove_if return an iterator at the first element that matched, which is the correct parameter to a call to erase. (In the case of no match, end() would be returned which is still a valid value to pass, and which translates to a no-op). The full construct looks like this for a container c for which any element with value -99 is to be removed (std:: prefixes not shown):

c.erase(remove(begin(c), end(c), -99), end(c));

Similarly for the same container which requires all numbers greater than 100 to be removed:

c.erase(remove_if(begin(c), end(c), [](auto& e){ return e > 100; }), end(c));

This idiom is often written on multiple lines, with suitable indentation to aid comprehension. Note that end(c) appears twice in each case, as it needs to be specified to both erase and remove/remove_if.

3 – decltype(auto)

It’s been part of Modern C++ so long it no longer feels “new”; auto is used to query the type of a variable or expression so the coder does not have to worry about “guessing” the correct type for a new variable. In fact decltype() performs the same task, and is compatible with just about any expression; also it does not strip const, volatile and reference (&, &&) qualifications, unlike auto.

There is a corner case for combining the two, related to automatic deduction of function return type; this would usually only be needed with generic functions (using template type parameters). If you see this in code, be aware that it means the same as plain auto semantically, however the function may be returning a reference value when the return expression is enclosed in parentheses. (This is the most usual case, but this return expression may be qualified with const and/or volatile as well).

The following program demonstrates use of decltype(auto) (instead of using auto& and auto respectively):

#include <iostream>

decltype(auto) print = (std::cout);  // parentheses are necessary here
decltype(auto) newline = &std::endl<char, std::char_traits<char>>;

int main() {
    print << "Hello decltype(auto)!" << newline;
}

4 – Copy and swap

The copy-and-swap idiom is used to provide the strong exception guarantee for some of the special member functions of classes and structs, they are the copy and move assignment operators as well as the move constructor. Consider the following class outline for a “smart” array:

template <typename T>
class Array {
    size_t array_size{};
    T *array_data{ nullptr };
    void swap(Array<T>& other) noexcept;
public:
    // other member functions not shown
};

The key thing to note is the private member function swap() which is declared noexcept, takes a reference to another Array<T> and returns void. This function provides a memberwise swap (using std::swap) as shown here:

template <typename T>
void Array<T>::swap(Array<T>& other) noexcept {
    using std::swap;  // scoped using-declaration
    swap(array_data, other.array_data);
    swap(array_size, other.array_size);
}

As all specializations of std::swap are noexcept, this function can also be declared noexcept. Of course, the actual calls to swap() would need to be changed based upon the class’s data members. The motivation for creating a swap member function is demonstrated by the correct version of the copy assignment operator for this class:

template <typename T>  // copy assignment
Array<T>& Array<T>::operator=(const Array<T>& rhs) {
    Array tmp{ rhs };
    tmp.swap(*this);  // or: swap(tmp);
    return *this;
}

Each of the three lines of the funtion body are significant here:

  1. A copy of the variable rhs is created, which uses the (usual) copy constructor (not shown). If this operation throws an exception, no memory is leaked (assuming the copy constructor is well written) and *this is unchanged.
  2. The member function swap() is called on tmp, making *this an exact copy of rhs, which is the desired outcome.
  3. The “old” *this (now tmp) goes out of scope, and is deallocated correctly. Remember that destructors are not allowed to throw exceptions, so this too is exception safe.

The move assignment operator is almost identical, the difference being that tmp is initialized from an r-value reference so std::move is used. The move constructor is trivial to write and is shown here too:

template <typename T>  // move assignment
Array<T>& Array<T>::operator=(Array<T>&& rhs) {
    Array tmp{ std::move(rhs) };
    tmp.swap(*this);
    return *this;
}

template <typename T>  // move constructor
Array<T>::Array(Array<T>&& other) {
    other.swap(*this);
}

It is also possible to define a free function swap() taking two references, making this a friend function so that it can perform the memberwise swap. Which variant is chosen is probably a matter of taste.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s