Modern C++
Programming
24. Software Design II [DRAFT]
Design Patterns and Idioms
Federico Busato
2024-03-29
Table of Contents
1 C++ Idioms
Rule of Zero
Rule of Three
Rule of Five
2 Design Pattern
Singleton
PIMPL
Curiously Recurring Template Pattern
Template Virtual Functions
1/15
C++ Idioms
Rule of Zero
The Rule of Zero is a rule of thumb for C++
Utilize the value semantics of existing types to avoid having to implement custom
copy and move operations
Note: many classes (such as std classes) manage resources themselves and should not
implement copy/move constructor and assignment operator
class X {
public:
X(...); // constructor
// NO need to define copy/move semantic
private:
std::vector<int> v; // instead raw allocation
std::unique_ptr<int> p; // instead raw allocation
}; // see smart pointer
2/15
Rule of Three
The Rule of Three is a rule of thumb for C++(03)
If your class needs any of
• a copy constructor X(const X&)
• an assignment operator X& operator=(const X&)
• or a destructor ∼X()
deﬁned explicitly, then it is likely to need all three of them
Some resources cannot or should not be copied. In this case, they should be declared
as deleted
X(const X&) = delete
X& operator=(const X&) = delete
3/15
Rule of Five
The Rule of Five is a rule of thumb for C++11
If your class needs any of
• a copy constructor X(const X&)
• a move constructor X(X&&)
• an assignment operator X& operator=(const X&)
• an assignment operator X& operator=(X&&)
• or a destructor ∼X()
deﬁned explicitly, then it is likely to need all ﬁve of them
4/15
Design Pattern
Singleton
Singleton is a software design pattern that restricts the instantiation of a class to one
and only one object (a common application is for logging)
class Singleton {
public:
static Singleton& get_instance() { // note "static"
static Singleton instance { ..init.. } ;
return instance; // destroyed at the end of the program
} // initiliazed at first use
Singleton(const& Singleton) = delete;
void operator=(const& Singleton) = delete;
void f() {}
private:
T _data;
Singleton( ..args.. ) { ... } // used in the initialization
}
5/15
PIMPL - Compilation Firewalls
Pointer to IMPLementation (PIMPL) idiom allows decoupling the interface from
the implementation in a clear way
header.hpp
class A {
public:
A();
∼A();
void f();
private:
class Impl; // forward declaration
Impl* ptr; // opaque pointer
};
NOTE: The class does not expose internal data members or methods
6/15
PIMPL - Implementation
source.cpp (Impl actual implementation)
class A::Impl { // could be a class with a complex logic
public:
void internal_f() {
..do something..
}
private:
int _data1;
float _data2;
};
A::A() : ptr{new Impl()} {}
A::∼A() { delete ptr; }
void A::f() { ptr->internal_f(); }
7/15
PIMPL - Advantages, Disadvantages
Advantages:
• ABI stability
• Hide private data members and methods
• Reduce compile type and dependencies
Disadvantages:
• Manual resource management
- Impl* ptr can be replaced by unique ptr<impl> ptr in C++11
• Performance: pointer indirection + dynamic memory
- dynamic memory could be avoided by using a reserved space in the interface e.g.
uint8 t data[1024]
8/15
PIMPL - Implementation Alternatives
What parts of the class should go into the Impl object?
• Put all private and protected members into Impl :
Error prone. Inheritance is hard for opaque objects
• Put all private members (but not functions) into Impl :
Good. Do we need to expose all functions?
• Put everything into Impl , and write the public class itself as only the public
interface, each implemented as a simple forwarding function:
Good
https://herbsutter.com/gotw/ 100/
9/15
Curiously Recurring Template Pattern 1/3
The Curiously Recurring Template Pattern (CRTP) is an idiom in which a class
X derives from a class template instantiation using X itself as template argument
A common application is static polymorphism
template <class T>
struct Base {
void my_method() {
static_cast<T*>(this)->my_method_impl();
}
};
class Derived : public Base<Derived> {
// void my method() is inherited
void my_method_impl() { ... } // private method
};
10/15
Curiously Recurring Template Pattern 2/3
# include <iostream>
template <typename T>
struct Writer {
void write(const char* str) {
static_cast<const T*>(this)->write_impl(str);
}
};
class CerrWriter : public Writer<CerrWriter> {
void write_impl(const char* str) { std::cerr << str; }
};
class CoutWriter : public Writer<CoutWriter> {
void write_impl(const char* str) { std::cout << str; }
};
CoutWriter x;
CerrWriter y;
x.write("abc");
y.write("abc");
11/15
Curiously Recurring Template Pattern 3/3
template <typename T>
void f(Writer<T>& writer) {
writer.write("abc);
}
CoutWriter x;
CerrWriter y;
f(x);
f(y);
CRTP C++ Examples
12/15
Template Virtual Function 1/3
Virtual functions cannot have template arguments, but they can be emulated by
using the following pattern
class Base {
public:
template<typename T>
void method(T t) {
v_method(t); // call the actual implementation
}
protected:
virtual void v_method(int t) = 0; // v_method is valid only
virtual void v_method(double t) = 0; // for "int" and "double"
};
13/15
Template Virtual Function 2/3
Actual implementations for derived class A and B
class AImpl : public Base {
protected:
template<typename T>
void t_method(T t) { // template "method()" implementation for A
std::cout << "A " << t << std::endl;
}
};
class BImpl : public Base {
protected:
template<typename T>
void t_method(T t) { // template "method()" implementation for B
std::cout << "B " << t << std::endl;
}
};
14/15
Template Virtual Function 3/3
template<class Impl>
class DerivedWrapper : public Impl {
private:
void v_method(int t) override {
Impl::t_method(t);
}
void v_method(double t) override {
Impl::t_method(t);
} // call the base method
};
using A = DerivedWrapper<AImpl>;
using B = DerivedWrapper<BImpl>;
int main(int argc, char* argv[]) {
A a;
B b;
Base* base = nullptr;
base = &a;
base->method(1); // print "A 1"
base->method(2.0); // print "A 2.0"
base = &b;
base->method(1); // print "B 1"
base->method(2.0); // print "B 2.0"
}
method() calls v method() (pure virtual method of Base )
v method() calls t method() (actual implementation)
15/15