Microproject: OptionalScopedObject template class

(Published in similar form GitHub)

Recently I ran into a weird situation where a threading lock was either passed, or not (since locking might or might not be appropriate in a given situation). Since I am always very concerned about other people adding early out return statements and forgetting to do the cleanup (here: unlocking the lock), I wanted to use a scoped version of the lock. But having the scope optional forced me into using an allocation:

void somethingProbablyUnsafe(ThreadingLock* lock)
{
    // unrelated code
    // ...

    std::unique_ptr<ScopedLock> scopedLock;
    if(lock)
    {
        scopedLock.reset(new ScopedLock(*lock));
    }

    // Complicated code with early outs.
    // ...
}

This certainly solves the problem – the lock is still scoped and nobody needs to worry about having the lock unlocked. Way better than checking the lock variable at each return point. But it requires an allocation and no longer keeps the ScopedLock on the stack as it is intended to be, how awful! 😦

Template programming for the rescue! Using variadic templates and placement constructors we can solve this problem in no time once and for all! 🙂

// Class wrapper to provide an optional scoped object on the stack without any dynamic allocations.
template<typename ScopedObject>
class OptionalScopedObject
{
public:
	// Creates a new inactive object. The underlying ScopedObject type is not initialized!
	OptionalScopedObject() : m_active(false)
	{
	}

	// If the underlying ScopedObject was created via construct, it will be deconstructed.
	~OptionalScopedObject()
	{
		destruct();
	}

	OptionalScopedObject(OptionalScopedObject&) = delete;
	void operator = (OptionalScopedObject&) = delete;

	// Constructs the object. Will destruct the object first if there was already one.
	template<typename... Args>
	void construct(Args... args)
	{
		if (m_active)
		{
			(*this)->~ScopedObject();
		}
		new (m_memory) ScopedObject(args...);
		m_active = true;
	}

	// Destructs the underlying object if one has been previously constructed.
	void destruct()
	{
		if (m_active)
		{
			(*this)->~ScopedObject();
			m_active = false;
		}
	}


	// Access to underlying object.
	const ScopedObject* operator -> () const
	{
		return reinterpret_cast<const ScopedObject*>(m_memory);
	}
	const ScopedObject& operator * () const
	{
		return *reinterpret_cast<const ScopedObject*>(m_memory);
	}
	ScopedObject* operator -> ()
	{
		return reinterpret_cast<ScopedObject*>(m_memory);
	}
	ScopedObject& operator * ()
	{
		return *reinterpret_cast<ScopedObject*>(m_memory);
	}

	// Weather the underlying object is constructed or not.
	operator bool() const
	{
		return m_active;
	}

private:
	char m_memory[sizeof(ScopedObject)];
	bool m_active;
};

Usage:

void somethingProbablyUnsafe(ThreadingLock* lock)
{
    // unrelated code
    // ...

    OptionalScopedObject<ScopedLock> scopedLock;
    if(lock)
    {
        scopedLock.construct(*lock);
    }

    // Complicated code with early outs.
    // ...
}

Scoped thread locks are of course only an example. The template can be used with any kind of scoped object that may or may not be required.

All this almost let forget you about the simple solution: Your scoped object should take a pointer and it should handle null. (yeah, I forgot about that myself when I had this whole idea, thank you for pointing that out to me @ Christopher)