Resource Acquisition is Initialization (RAII)

Table of Contents

  • 1. Example of `Mutex_lock()`
    • 1.1 Bad Performance
    • 1.2 Good Performance
  • 2. Example of `std::share_ptr`

1. Example of Mutex_lock()

1.1 Bad Performance

Mutex_t mu = MUTEX_INITIALIZER();

void functionA(){
	Mutex_lock(&mu);
	... // Do a bunch of things
	Mutex_unlock(&mu);	// Bad performacne. We can't guarentee this line will always be executed.
}

1.2 Good Performance

class Lock{
private:
	Mutext_t *m_pm;
public:
	explicit Lock(Mutex_t *pm){
		Mutex_lock(pm);
		m_pm = pm;
	}
	~Lock(){ Mutex_unlock(m_pm); }
}

void functionA() {
	Lock mylock(&mu);
	... // Do a bunch of things
	// The mutex will always be released when mylock is destroied from stack.
}

Conclusion: The only way that can guarantee the code to be executed after exception is thrown are the destructor of the objects residing in the stack.

Resource management therefore needs to be tied to the lifespan of suitable objects in order to gain automatic deallocation and reclamation.

2. Example of std::share_ptr

int functionA(){
	std::shared_ptr<dog> pd(new dog());
	...
}
// The dog is destructed when pd goes out of scope (no more pointer points to pd)

Question: What is the problem with the following code?

// Another example:
class dog;
class Trick;
void train(std::shared_ptr<dog> pd, Trick dogtrick);
Trick getTrick();

int main() {
	train(std::shared_ptr<dog>(new dog()), getTrick());
}

What happens in train()'s parameter passing?

  1. new dog()
  2. getTrick()
  3. construct std::shared_ptr

The order of these operations are determined by compiler. If compiler performs the operations in the above order, we many have an issue.

If getTrick() throws an exception, then the construction of shared_ptr fails, but since now we already have an new dog() which means we will have memory leakage.

Conclusion: Don’t combine the operation of storing objects in a shared pointer with any other statement. In other words, put the storing objects in shared pointer statement to a standalone statement. So in this example, we should write like that

int main() {
	std::shared_ptr<dog> pd(new dog());
	train(pd, getTrick())
}

What happens when resource Management Object is copied

// construct L1 by mu and copy L1 and construct L2 
Lock L1(&mu);
Lock L2(L1);

Usually the mutex is mutually exclusive so it can’t be shared by multiple clients.

Solution1: Prohibit copying. Disallow copy constructor and copy assignment operator from being used.

Solution 2: Allow the resource to be shared and guarantee the resources will be released appropriately when all the clients are done with it. Reference-count the underlying resource by using std::shared_ptr

// Constructor of shared_ptr can take the second parameter: a deleter
// a deleter is a function that will be invoked when the shared pointer is destroyed.
template<class Other, class D>
shared_ptr(Other *ptr, D deleter);

// The dafault value for the deleter is operator "delete"
std::shared_ptr<dog> pd(new dog());

class Lock {
private:
	std::shared_ptr<Mutex_t> pMutex;
public:
	explicit Lock(Mutex_t *pm) : pMutex(pm, Mutex_unlock) {
		Mutex_lock(pm);
		// The second parameter of shared_ptr constructor is "deleter" function.
	}
}

int main() {
	Lock L1(&mu);
	Lock L2(L1);
}

Explanation: Lock L1(&mu); The Lock class get a mutex pointer and use it along with its unlock function to construct a shared pointer. Then in the function body it locks the mutex.
Lock L2(L1); create another pMutex pointer point to the pm, if we use L2.pMutex.use_count() we will get 2.
The Mutex_unlock() will only be invoked when no pointer pointing to pm.

你可能感兴趣的:(C/C++)