Previously, we looked at some of the foundational C++ code kata – that is, elements of C++ coding that are absolutely key to master if you’re going to be programming in C++.
Practice makes perfect, part 1 – Code kata
Practice makes perfect, part 2 – foundation kata
In this article I want to introduce what I call ‘idiomatic’ kata. These exercises have a bit more latitude (and variation) in how they can be implemented. In that respect they are closer to traditional code kata. The idea with these kata is to reinforce C++ constructs that aren’t encountered quite so often in typical C++ programming and so are more easily forgotten (or even avoided)
There’s no order to these kata. None is more important than any other.
If you’re new to C++ there’s nothing wrong with practicing these exercises; but use them as a means to explore new language features. It’s good to learn how these idiomatic patterns work.
Contents
pImpl idiom
Why is this important?
Breaks dependencies between users of a class and its implementation
Example
#include <memory> class ADT { public: ADT(); void op_A(); void op_B(); private: class Impl; std::unique_ptr<Impl> impl; }; ADT::ADT() : impl { std::make_unique<ADT::Impl>() } { } class ADT::Impl { public: void op_A() { /* ... */ } void op_B() { /* ... */ } }; void ADT::op_A() { impl->op_A(); } void ADT::op_B() { impl->op_B(); } int main() { ADT adt { }; adt.op_A(); }
Object adapter
Why is this important?
Provides a new interface for an existing utility
Read more here.
Variations
- Use interfaces for existing API and required API
Example
class Service { public: virtual ~Service() = default; virtual void service1() = 0; virtual void service2() = 0; }; class Utility { public: void func1() { /* ... */ } void func2() { /* ... */ } protected: void helper_function() { /* ... */ } }; class Client { public: void run(); private: Service* server { nullptr }; friend void connect(Client& client, Service& srv); }; void Client::run() { server->service1(); } void connect(Client& client, Service& srv) { client.server = &srv; } class Object_adapter : public Service { protected: void service1() override { utility.func1(); } void service2() override { utility.func2(); } private: Utility utility { }; }; int main() { Client client { }; Object_adapter adapter { }; connect(client, adapter); client.run(); }
Class adapter
Why is this important?
Provides a new interface for an existing utility
Practical application of private inheritance
Useful for providing ‘subset’ interfaces of existing types
Simulates derived (non-substitutable) type, versus sub-typing (substitution)
Variations
- Use interfaces for existing API and required API
Example
#include <vector> enum class Event { advisory, caution, warning }; class Event_list : private std::vector<Event> { public: using Base = std::vector<Event>; // Modified API // void add(Event e) { Base::push_back(e); } // Subset API // using Base::Base; using Base::begin; using Base::end; using Base::size; }; int main() { Event_list events { Event::warning, Event::warning, Event::caution }; for (auto e : events) { // ... } }
Circular buffer
Why is this important?
A fixed-size FIFO is not part of the STL
Very commonly used in embedded systems
A nice exercise for manipulation of iterators on containers
Variations
- Throw exceptions on empty/full
- Return tuple from ‘get’ function
- Return std::optional from ‘get’ function
- Support ‘add’ by move and copy
- Support ‘add’ by move and copy via perfect forwarding
- Make thread-safe (using an adapter)
Example
#include <cstddef> #include <array> #include <optional> template<typename T = int, std::size_t sz = 8> class Buffer { public: Buffer() = default; template <typename U> bool add(U&& in_val); std::optional<T> get(); inline bool is_empty() const; inline std::size_t size() const; private: using Container = std::array<T, sz>; using Iterator = typename Container::iterator; Container buffer { }; Iterator read { std::begin(buffer) }; Iterator write { std::begin(buffer) }; unsigned num_items { 0 }; }; template<typename T, std::size_t sz> template<typename U> bool Buffer<T, sz>::add(U&& in_val) { if (num_items == sz) return false; *write = std::forward<U>(in_val); ++num_items; ++write; if (write == std::end(buffer)) write = std::begin(buffer); return true; } template<typename T, std::size_t sz> std::optional<T> Buffer<T, sz>::get() { if (num_items == 0) return std::nullopt; auto selected = read; --num_items; ++read; if (read == std::end(buffer)) read = std::begin(buffer); return std::move(*selected); } template<typename T, std::size_t sz> bool Buffer<T, sz>::is_empty() const { return (num_items == 0); } template<typename T, std::size_t sz> std::size_t Buffer<T, sz>::size() const { return num_items; }
Port
Why is this important?
Implementation of UML ports
Allows a class to realize multiple interfaces without multiple inheritance
For more information have a look at this article.
Variations
- Delegate to an internal implementation
Example
#include <iostream> using namespace std; class Interface_1 { public: virtual ~Interface_1() = default; virtual void op() = 0; }; class Interface_2 { public: virtual ~Interface_2() = default; virtual void op() = 0; }; // Class Implementation cannot realize // both interfaces, so use a port for // each interface // class Implementation { private: class Port_1 : public Interface_1 { protected: void op() override { cout << "Port_1 realization" << endl; } }; class Port_2 : public Interface_2 { protected: void op() override { cout << "Port_2 realization" << endl; } }; public: // Port objects // Port_1 port_1 { }; Port_2 port_2 { }; }; class Client_1 { public: void run() { server->op(); } private: Interface_1* server { nullptr }; friend void connect(Client_1& client, Interface_1& srv); }; void connect(Client_1& client, Interface_1& srv) { client.server = &srv; } class Client_2 { public: void run() { server->op(); } private: Interface_2* server { nullptr }; friend void connect(Client_2& client, Interface_2& srv); }; void connect(Client_2& client, Interface_2& srv) { client.server = &srv; } int main() { Implementation impl { }; Client_1 client_1 { }; Client_2 client_2 { }; connect(client_1, impl.port_1); connect(client_2, impl.port_2); client_1.run(); client_2.run(); }
Copy-swap idiom
Why is this important?
Idiomatic implementation for deep-copy and move-semantics on resource-owning types.
Variations
- Implement move-only types
Example
#include <iostream> #include <cstring> class Simple_string { public: Simple_string() = default; Simple_string(const char* str); friend std::ostream& operator<<( std::ostream& os, const Simple_string& str ); // Copy and move policy (deep-copy) // ~Simple_string(); Simple_string(const Simple_string& other); Simple_string(Simple_string&& other) noexcept; Simple_string& operator=(Simple_string rhs); friend void swap(Simple_string& lhs, Simple_string& rhs) noexcept; private: char* raw { nullptr }; }; Simple_string::Simple_string(const char* str) { if (str) { auto num_chars = strlen(str); raw = new char[ num_chars + 1 ]; strncpy(raw, str, num_chars); } } Simple_string::~Simple_string() { delete[] raw; } Simple_string::Simple_string(const Simple_string& other) : Simple_string { other.raw } { } Simple_string::Simple_string(Simple_string&& other) noexcept : Simple_string { } { swap(*this, other); } Simple_string& Simple_string::operator=(Simple_string rhs) { swap(*this, rhs); return *this; } std::ostream& operator<<(std::ostream& os, const Simple_string& str) { if (str.raw) os << str.raw; return os; } void swap(Simple_string& lhs, Simple_string& rhs) noexcept { using std::swap; swap(lhs.raw, rhs.raw); } int main() { Simple_string s1 { }; Simple_string s2 { "Hello world" }; s1 = s2; std::cout << s1 << std::endl; std::cout << s2 << std::endl; }
Variadic template function
Why is this important?
Reinforces syntax of variadic templates
Variations
- Add a non-variadic template parameter
- Add a non-template-type parameter
Example
#include <iostream> using namespace std; template <typename T, typename... Other_Ty> void print(T&& first, Other_Ty&&... others) { cout << first << " "; print(std::forward<Other_Ty>(others)...); } // Overload with no parameters to // terminate template recursion // void print() { cout << endl; } int main() { print(1, 2, 14.7); print(34.5); print(); }
Variadic base classes
Why is this important?
Reinforces syntax of variadic templates
Useful implementation for mix-in classes
For an application of this technique, have a look at this article.
Variations
- Expose base class member functions
Example
#include <iostream> using namespace std; template <typename... Mixin_Ty> class Utility : public Mixin_Ty... { public: using Mixin_Ty::operator()...; }; class Feature1 { public: void operator()(int val) { cout << "Feature1 " << val << endl; } }; class Feature2 { public: void operator()() { cout << "Feature 2" << endl; } }; int main() { Utility<Feature1, Feature2> utility { }; utility(); utility(100); }
Class template deduction guide
Why is this important?
Simplifies template class code
Replaces factory functions in a lot of cases
Reinforces syntax.
For more information have a look at this article.
Variations
- Deduction guide for class with no constructor
- Deduction guide for class constructor with reference parameters
- Non-template-type deduction guide
Example
#include <iostream> using namespace std; template <typename T> class ADT { public: ADT(const T& init) : value { init } { } void show_type() const { cout << typeid(value).name() << endl; } private: T value { }; }; // Deduction guide // template <typename T> ADT(T) -> ADT<T>; // Explicit deduction guide for // string literals. // ADT(const char*) -> ADT<std::string>; int main() { ADT adt1 { 55 }; // T => int ADT adt2 { 12.7 }; // T => double ADT adt3 { "Hello" }; // T => std::string adt1.show_type(); adt2.show_type(); adt3.show_type(); }
Forwarding reference idiom
Why is this important?
Forms the basis of ‘make’ functions
Allows ‘by-copy’ and ‘by-move’ variations of functions
Variations
- Use a variadic template function
Example
#include <iostream> using namespace std; class ADT { public: ADT(int init) : value { init } { } void op() const { cout << value << endl; } private: int value { }; }; template <typename Made_Ty, typename... Param_Ty> Made_Ty make_thing(Param_Ty&&... arg) { return Made_Ty { std::forward<Param_Ty>(arg)... }; } int main() { auto val1 = make_thing<double>(100.7); auto val2 = make_thing<ADT>(100); }
Curiously Recurring Template Pattern
Why is this important?
CRTP is a counter-intuitive usage of templates.
Allows a template implementation of ‘static polymorphism’
Template implementation of ‘method chaining’
Variations
- Implement generic object counter
- Implement generic ABC
- Implement polymorphic chaining
Example
#include <iostream> using namespace std; template <typename Derived_Ty> class Base { public: void op1() { actual().op1_impl(); } void op2() { actual().op2_impl(); } private: void op1_impl() { cout << "Base op1" << endl; } void op2_impl() { cout << "Base op2" << endl; } // Helper function to improve readability // Derived_Ty& actual() { return *(static_cast<Derived_Ty*>(this)); } }; class Derived : public Base<Derived> { public: void op1() { cout << "Derived op1" << endl; } }; class Another : public Base<Derived> { public: void op2() { cout << "Another op2" << endl; } }; int main() { Derived d1 { }; d1.op1(); d1.op2(); Another a1 { }; a1.op1(); a1.op2(); }
Template policy
Why is this important?
Template replacement for client-server association
For more information have a look at this article.
Variations
- Client creates server object (composition)
- Client has association to object
Example
#include <iostream> using namespace std; template <typename Server_Ty> class Client { public: Client(Server_Ty& srv) : server { &srv} { } void run() { // Contractual obligation to support // do_stuff(); // server->do_stuff(); } private: Server_Ty* server { nullptr }; }; // Template deduction guide allows Server_Ty // to be deduced as the association between client // and server is formed (via the ctor) // template <typename T> Client(T) -> Client<T>; class ADT { public: int do_stuff() { cout << "ADT" << endl; return 0; } }; class Another { public: void do_stuff() { cout << "Hello" << endl; } void do_more() { cout << "World" << endl; } }; int main() { Another server { }; Client client { server }; client.run(); }
Class template specialisation
Why is this important?
Provide specialized implementations for particular types
Variations
- Explicit specialization
- Partial specialization
Example
#include <iostream> using namespace std; template <typename T> class Processor { public: Processor(const T& init) : value { init} { } void process() { cout << "Base implementation" << endl; } private: T value { }; }; template <typename T> Processor(T) -> Processor<T>; template <typename T> class Processor<T*> { public: Processor(T* init) : value { init} { } void process() { cout << "Pointer implementation" << endl; } private: T* value { }; }; template <typename T> Processor(T*) -> Processor<T*>; template <> class Processor<std::string> { public: Processor(std::string init) : value { std::move(init) } { } void process() { cout << "std::string implementation" << endl; } private: std::string value { }; }; Processor(const char*) -> Processor<std::string>; int main() { Processor p1 { 100 }; p1.process(); int i; Processor p2 { &i }; p2.process(); Processor p3 { "Hello, world!" }; p3.process(); }
Function template specialisation – The Dimov/Abrahams example
Why is this important?
Understanding the Dimov/Abrahams example for function template specialization is the key to knowing why you shouldn’t ever specialize template functions!
See Herb Sutter’s article for more detail.
Variations
- Overload with non-template functions
Example
#include <iostream> using namespace std; template <typename T> void process(T param) { cout << "process for T" << endl; } // Placed here, this becomes a specialization // of void process(T) // // template <> // void process<>(int* param) // { // cout << "Explicit specialization for int*" << endl; // } template <typename T> void process(T* param) { cout << "specialization for T*" << endl; } // Placed here, this becomes a specialization // of void process<>(int*) // template <> void process<>(int* param) { cout << "Explicit specialization for int*" << endl; } int main() { int i { }; process(i); process(&i); }
enable_if
Why is this important?
Conditional compilation idiom for template functions
For more information, and application, of this technique have a look at this article.
Variations
- SFINAE via invalid template parameter
- SFINAE via invalid return type
- SFINAE via invalid function parameter
- Use type alias to improve code readability
Example
#include <iostream> #include <type_traits> using namespace std; // Aliases to aid readability // template <typename T> using is_floating_type = std::enable_if<std::is_floating_point<T>::value, bool>::type; template <typename T> using is_other_type = std::enable_if<!std::is_floating_point<T>::value, bool>::type; // A - only enabled for floating-point types // template <typename T, is_floating_type<T> = true> void process(T param) { cout << param << " is a floating point type" << endl; } // B - Enabled for all non-floating-point types // template <typename T, is_other_type<T> = true> void process(T param) { cout << param << " is some other type" << endl; } int main() { process(100); // calls (B) process(17.6); // calls (A) }
Summary
Repetition is the key to competence. More importantly, you should practice the things you are bad at, the things you find most difficult, more often.
This set of kata represent a small set of all the possible exercises you could (and should) practice in C++. I’m sure you have your own. I’d be interested to hear the kata you regularly practice.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.