The Null Object Pattern

Contents[Show]

A Null Object encapsulates a do nothing behavior inside an object. It is often pretty comfortable to use a neutral object.

 ClassIdioms

A Null Object

  • encapsulates the do nothing behavior inside an object.
  • supports the workflow without conditional logic.
  • hides the special use cases from the client.

Honestly, there is not much to write about the Null Object. Let me, therefore, give you an example, using a Null Object.

Strategized Locking

Assume you write code such as a library, which should be used in various domains, including concurrent ones. To be on the safe side, you protect the critical sections with a lock. If your library now runs in a single-threaded environment, you have a performance issue because you implemented an expensive synchronization mechanism that is unnecessary. Now, strategized locking comes to your rescue.

Strategized locking is the idea of the Strategy Pattern applied to locking. This means putting your locking strategy into an object and making it into a pluggable component of your system.

There are two typical ways to implement strategized locking: run-time polymorphism (object orientation) or compile-time polymorphism (templates).
Both ways improve the customization and extension of the locking strategy, ease the maintenance of the system, and support the reuse of components. Also, implementing the strategized locking at run-time or compile-time differ in various aspects.

Advantages

Run-Time Polymorphism

  • Allows it to configure the locking strategy during run time
  • Is easier to understand for developers who have an object-oriented background

Compile-Time Polymorphism

  • Has no abstraction penalty
  • Has a flat hierarchy.

Disadvantages

Run-Time Polymorphism

  • Needs an additional pointer or reference indirection
  • It may have a deep derivation hierarchy

Compile-Time Polymorphism

  • It may generate very wordy error messages.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Be part of my mentoring programs:

 

 

 

 

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

Implementation based on Run-Time Polymorphism

 The program strategizedLockingRuntime.cpp presents three different mutexes.

 

// strategizedLockingRuntime.cpp

#include <iostream>
#include <mutex>
#include <shared_mutex>

class Lock {
public:
    virtual void lock() const = 0;
    virtual void unlock() const = 0;
};

class StrategizedLocking {
    Lock& lock;                                  // (1)
public:
    StrategizedLocking(Lock& l): lock(l){
        lock.lock();                             // (2)
    }
    ~StrategizedLocking(){
        lock.unlock();                           // (3)
    }
};

struct NullObjectMutex{                          
    void lock(){}
    void unlock(){}
};

class NoLock : public Lock {                     // (4)
    void lock() const override {
        std::cout << "NoLock::lock: " << '\n';
        nullObjectMutex.lock();
    }
    void unlock() const override {
        std::cout << "NoLock::unlock: " << '\n';
         nullObjectMutex.unlock();
    }
    mutable NullObjectMutex nullObjectMutex;     // (9)
};

class ExclusiveLock : public Lock {              // (5)
    void lock() const override {
        std::cout << "    ExclusiveLock::lock: " << '\n';
        mutex.lock();
    }
    void unlock() const override {
        std::cout << "    ExclusiveLock::unlock: " << '\n';
        mutex.unlock();
    }
    mutable std::mutex mutex;                    // (10)
};

class SharedLock : public Lock {                 // (6)
    void lock() const override {
        std::cout << "        SharedLock::lock_shared: " << '\n';
        sharedMutex.lock_shared();                // (7)
    }
    void unlock() const override {
        std::cout << "        SharedLock::unlock_shared: " << '\n';
        sharedMutex.unlock_shared();              // (8)
    }
    mutable std::shared_mutex sharedMutex;        // (11)
};

int main() {
    
    std::cout << '\n';
    
    NoLock noLock;
    StrategizedLocking stratLock1{noLock};
    
    {
        ExclusiveLock exLock;
        StrategizedLocking stratLock2{exLock};
        {
            SharedLock sharLock;
            StrategizedLocking startLock3{sharLock};
        }
    }
    
    std::cout << '\n';
    
}

 

The class StrategizedLocking has a lock (line 1). StrategizedLocking models scoped locking and, therefore, locks the mutex in the constructor (line 2) and unlocks it in the destructor (line 3). Lock is an abstract class and defines all derived classes’ interfaces. These are the classes NoLock (line 4), ExclusiveLock (line 5), and SharedLock (line 6). SharedLock invokes lock_shared (line 7) and unlock_shared (line 8) on its std::shared_mutex. Each of these locks holds one of the mutexes NullObjectMutex (line 9), std::mutex (line 10), or std::shared_mutex (line 11). NullObjectMutex is a noop placeholder. The mutexes are declared as mutable. Therefore, they are usable in constant member functions such as lock and unlock.

Implementation based on Compile-Time Polymorphism

 The template-based implementation is quite similar to the object-oriented-based implementation.

 

// strategizedLockingCompileTime.cpp

#include <iostream>
#include <mutex>
#include <shared_mutex>


template <typename Lock>
class StrategizedLocking {
    Lock& lock;
public:
    StrategizedLocking(Lock& l): lock(l){
        lock.lock();
    }
    ~StrategizedLocking(){
        lock.unlock();
    }
};

struct NullObjectMutex {
    void lock(){}
    void unlock(){}
};

class NoLock{                                         // (1)
public:
    void lock() const {
        std::cout << "NoLock::lock: " << '\n';
        nullObjectMutex.lock();
    }
    void unlock() const {
        std::cout << "NoLock::unlock: " << '\n';
         nullObjectMutex.lock();
    }
    mutable NullObjectMutex nullObjectMutex;
};

class ExclusiveLock {                                  // (2)
public:
    void lock() const {
        std::cout << "    ExclusiveLock::lock: " << '\n';
        mutex.lock();
    }
    void unlock() const {
        std::cout << "    ExclusiveLock::unlock: " << '\n';
        mutex.unlock();
    }
    mutable std::mutex mutex;
};

class SharedLock {                                     // (3)
public:
    void lock() const {
        std::cout << "        SharedLock::lock_shared: " << '\n';
        sharedMutex.lock_shared();
    }
    void unlock() const {
        std::cout << "        SharedLock::unlock_shared: " << '\n';
        sharedMutex.unlock_shared();
    }
    mutable std::shared_mutex sharedMutex;
};

int main() {

    std::cout << '\n';
    
    NoLock noLock;
    StrategizedLocking<NoLock> stratLock1{noLock};
    
    {
        ExclusiveLock exLock;
        StrategizedLocking<ExclusiveLock> stratLock2{exLock};
        {
            SharedLock sharLock;
            StrategizedLocking<SharedLock> startLock3{sharLock};
        }
    }
    
    std::cout << '\n';

}

 

The programs strategizedLockingRuntime.cpp and strategizedLockingCompileTime.cpp produce the same output:

strategizedLocking

The locks NoLock (line 1), ExclusiveLock (line 2), and SharedLock (line 3) have no abstract base class. The consequence is that StrategizedLocking can be instantiated with an object that does not support the right interface. This instantiation would end in a compile-time error. This loophole is closed with C++20.

The Concept BasicLockable

Instead of template <typename Lock> class StrategizedLocking you can use the
concept BasicLockable: template <BasicLockable Lock> class StrategizedLocking. This means that all used locks have to support the concept BasicLockable. A concept is a named requirement, and many concepts are already defined in the C++20 concepts library. The concept BasicLockable is only used in the text of the C++20 standard. Consequently, I define and use the concept BasicLockable in the following improved implementation of the strategized locking at compile time.

// strategizedLockingCompileTimeWithConcepts.cpp

#include <iostream>
#include <mutex>
#include <shared_mutex>

template <typename T>                     // (1)
concept BasicLockable = requires(T lo) {
    lo.lock();
    lo.unlock();
};
    
template <BasicLockable Lock>             // (2)
class StrategizedLocking {
    Lock& lock;
public:
    StrategizedLocking(Lock& l): lock(l){
        lock.lock();
    }
    ~StrategizedLocking(){
        lock.unlock();
    }
};

struct NullObjectMutex {
    void lock(){}
    void unlock(){}
};

class NoLock{
public:
    void lock() const {
        std::cout << "NoLock::lock: " << '\n';
        nullObjectMutex.lock();
    }
    void unlock() const {
        std::cout << "NoLock::unlock: " << '\n';
         nullObjectMutex.lock();
    }
    mutable NullObjectMutex nullObjectMutex;
};

class ExclusiveLock {
public:
    void lock() const {
        std::cout << "    ExclusiveLock::lock: " << '\n';
        mutex.lock();
    }
    void unlock() const {
        std::cout << "    ExclusiveLock::unlock: " << '\n';
        mutex.unlock();
    }
    mutable std::mutex mutex;
};

class SharedLock {
public:
    void lock() const {
        std::cout << "        SharedLock::lock_shared: " << '\n';
        sharedMutex.lock_shared();
    }
    void unlock() const {
        std::cout << "        SharedLock::unlock_shared: " << '\n';
        sharedMutex.unlock_shared();
    }
    mutable std::shared_mutex sharedMutex;
};

int main() {

    std::cout << '\n';
    
    NoLock noLock;
    StrategizedLocking<NoLock> stratLock1{noLock};
    
    {
        ExclusiveLock exLock;
        StrategizedLocking<ExclusiveLock> stratLock2{exLock};
        {
            SharedLock sharLock;
            StrategizedLocking<SharedLock> startLock3{sharLock};
        }
    }
    
    std::cout << '\n';

}

 

BasicLockable in line (1) requires that an object lo of the type T that it has to support the member functions lock and unlock. The use of the concept is straightforward. Instead of typename, I use the concept BasicLockable in the template declaration of StrategizedLocking (line 2).

What's Next?

To use your user-defined type in a range-based for-loop, you have to implement the Iterator Protocol. Let me discuss the details in my next post.

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, and Rob North.

 

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

 

My special thanks to Take Up Code TakeUpCode 450 60

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

 

 

Tags: lock, mutex

Stay Informed about my Mentoring

 

Mentoring

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Course: The All-in-One Guide to C++20

Course: Master Software Design Patterns and Architecture in C++

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 4263

Yesterday 6193

Week 10456

Month 32130

All 12110339

Currently are 180 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments