Skip to content

Instantly share code, notes, and snippets.

@vishal-keshav
Last active January 25, 2019 10:12
Show Gist options
  • Save vishal-keshav/36ec002fd55fe427673634bf78b4717a to your computer and use it in GitHub Desktop.
Save vishal-keshav/36ec002fd55fe427673634bf78b4717a to your computer and use it in GitHub Desktop.
On Object oriented programming and design patterns in C++ part 3

Design Principles Part 3

RAII pattern

Resource allocation is initialization: This is an important design pattern mostly followed to operate on threading safely.

To understand the criticality of usage of RAII pattenn, we can see the code below.


#include "stdafx.h"
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

int i; //Global variable
mutex m;

void F1(){
	m.lock();
	i++;
	cout << "F1: " << i << endl;
	i++;
	cout << "F1: " << i << endl;
	return; // Here exception may occur
	m.unlock();
}

void F2(){
	cout << "I am starving" << endl;
	m.lock();
	i++;
	cout << "F2: " << i << endl;
	i++;
	cout << "F2: " << i << endl;
	m.unlock();
}

int main()
{
	thread th1(F1);
	F2();
	th1.join();

	return 0;
}

Above, we see that the main thread can go in infinite starvation.

To gaurantee that no such starvation happens, compiler has to gaurantee that unlock happens. This is possible in the deconstrutor call. When object goes out-of-the-scope, the deconstructor is automatically called, and this is where the unlock on mutex should be done.

Below code implements the RAII pattern, to safegurad the main program against such starvation.

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

int i; //Global variable
mutex m;

//Here we are implementing RAII pattern
// It needs a reference variable and initialize it.
template <class T>
class guard
{
private:
	T &m; // reference variable (this reference passing is important)
public:
	guard(T &m) : m(m) // Initialize the reference
	{
		m.lock();
	}
	//Destructor, this is guaranteed by the compiler that will call it
	~guard()
	{
		m.unlock();
	}
};

void F1(){
	//m.lock();
	guard<mutex> g1(m);
	i++;
	cout << "F1: " << i << endl;
	i++;
	cout << "F1: " << i << endl;
	return; // Here exception may occur
	//m.unlock();
}

void F2(){
	//m.lock();
	guard<mutex> g2(m);
	i++;
	cout << "F2: " << i << endl;
	i++;
	cout << "F2: " << i << endl;
	//m.unlock();
}

int main()
{
	thread th1(F1);
	F2();
	th1.join();

	return 0;
}

In the above code, when F1() exits, the objects (gaurd object) goes out of scope and the deconstructor is called. So, if an exception happens in the F1(), the proper unlocking if the mutes is guaranteed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment