Contents
Introduction & Background
Goal of this Article
This article aims to introduce a simple-look scope guard, or a substitute for BOOST_SCOPE_EXIT, for Visual C++ 2010 (and of course VC++2012 and 2013 as well), and explain its implementation details to beginners.
According to '
More C++ Idioms', the scope guard does not only ensure the resource deallocation, but also allows canceling it. So, strictly saying, the thing referred in this article is not a perfect scope guard. However, I simply call it 'scope guard' for ease.
This is the real-world example using my scope guard (SCOPE_EXIT
macro), a part of WinMain()
function of a WTL based program.
CComModule _Module;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
if (FAILED(::CoInitialize(nullptr)))
return 0;
SCOPE_EXIT { ::CoUninitialize(); };
ULONG_PTR gdipToken;
Gdiplus::GdiplusStartupInput gsi;
if (Gdiplus::GdiplusStartup(&gdipToken, &gsi, nullptr) != Gdiplus::Ok)
return 0;
SCOPE_EXIT { Gdiplus::GdiplusShutdown(gdipToken); };
if (FAILED(_Module.Init(nullptr, hInstance)))
return 0;
SCOPE_EXIT { _Module.Term(); };
return 0;
}
The SCOPE_EXIT
macros are executed in the reverse order as they appear. This is because the C++ language guarantees that the local variables are destructed in the reverse order as they have been constructed.
Do you think it is somewhat simpler than the one below?
About BOOST_SCOPE_EXIT
BOOST_SCOPE_EXIT
is a set of macros in the Boost library. It realizes the so-called scope guard in C++. It's pretty useful, but a little painful to use. Though I usually encourage my co-workers to incorporate the Boost into our job, some of its weak points have prevented me from recommending it. There are two points I want to improve. First, it's too striking. Second, it doesn't take an empty capture list. Look at this sample equivalent to above.
CComModule _Module;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
if (FAILED(::CoInitialize(nullptr)))
return 0;
int dummy;
BOOST_SCOPE_EXIT((&dummy)) {
::CoUninitialize();
} BOOST_SCOPE_EXIT_END;
ULONG_PTR gdipToken;
Gdiplus::GdiplusStartupInput gsi;
if (Gdiplus::GdiplusStartup(&gdipToken, &gsi, nullptr) != Gdiplus::Ok)
return 0;
BOOST_SCOPE_EXIT((&gdipToken)) {
Gdiplus::GdiplusShutdown(gdipToken);
} BOOST_SCOPE_EXIT_END;
if (FAILED(_Module.Init(nullptr, hInstance)))
return 0;
BOOST_SCOPE_EXIT((&_Module)) {
_Module.Term();
} BOOST_SCOPE_EXIT_END;
return 0;
}
We have to place two striking symbols both ahead and behind our code block. Though the functions like CoUninitialize()
should be main actors, the supporting actors steal the show away. Then, what is dummy? The macro needs at least one argument so we have to place a needless one there. We can use some existing variable like hInstance
instead of an especially declared one, but it may lead us to deeper confusion.
However, Visual C++ 2010 was released with some features of C++0x. The newly introduced features, especially lambda expression, inspired me to improve the weak points.
How It Works
Naïve Implementation
First of all, I have implemented a scope guard naïvely as the base of the further explanation.
The scope guard is a particular kind of RAII. The typical RAII classes allocate some resources in their constructors, and deallocate the resources in their destructors. Unlike the typical ones, the scope guards accept any user-defined functions via their constructors, and execute the functions in their destructors. Therefore, the least implementation is like this:
#include <iostream>
#include <functional>
class scope_exit_t
{
typedef std::function<void()> func_t;
public:
scope_exit_t(const func_t &f) : func(f) {}
~scope_exit_t() { func(); }
private:
const func_t func;
};
int main()
{
std::cout << "Resource Allocation." << std::endl;
scope_exit_t x = []{ std::cout << "Resource Deallocation" << std::endl; };
std::cout << "Some Work." << std::endl;
return 0;
}
In spite of its artlessness, it shows us some major improvements brought by C++0x. std::function<T>
type and lambda expression have made the code this short. Then let’s improve some problems left unsolved.
Prohibit Copying
Look at this code piece:
int main()
{
int *p = new int[10];
scope_exit_t x = [&]{ delete[] p; };
scope_exit_t y = x;
return 0;
}
A scope_exit_t
object is copied from x
to y
. p
will be deleted twice and the program will result in something unpredictable. Like this, resource cleanup functions must be called only once in general. So we'd better to prohibit copying the objects. There is a common practice that makes the job easy. All we have to do is to declare (and not to define) private
copy constructor and operator =
.
private:
scope_exit_t(const scope_exit_t &);
const scope_exit_t& operator=(const scope_exit_t &);
The dangerous code above can no longer compile.
(If you can use the Boost, it's easier to inherit boost::noncopyable to prohibit copying.)
Prohibit Operator new & delete
The operator new
is troublesome too.
int main()
{
scope_exit_t *p = new scope_exit_t([]{ std::cout << 1 << std::endl; });
delete p;
return 0;
}
In this code, the scope_exit_t
object will not be destructed without explicit delete
. It's a contradiction, since the RAII idiom is for removing such explicit resource deallocations. The scope_exit_t
objects should be created as local variables to work correctly. We'd better to prohibit the operator new
and delete
.
We can do it in a similar way as above. Declare them as the private
functions.
private:
void *operator new(size_t);
void *operator new[](size_t);
void operator delete(void *);
void operator delete[](void *);
Accept Rvalues, Reject Lvalues
The next problem is shown here:
int main()
{
std::function<void()> f = []{ std::cout << 1 << std::endl; }; scope_exit_t x = f;
f = []{ std::cout << 2 << std::endl; };
return 0;
}
Which function will run? The users cannot answer this question unless they examine the implementation of scope_exit_t
. Non-experts may stay in doubt even after that. It's so ambiguous. To avoid this ambiguousness, it's desirable that scope_exit_t
takes lambda expressions directly like this...
scope_exit_t x = []{ std::cout << 1 << std::endl; };
...and doesn’t take function objects already assigned to a variable. How can we implement this feature? Before thinking about it, we'd better know the difference between lvalues
and rvalues
. What are lvalues
and rvalues
? Though these terms give beginners headaches, the concept itself is simple. To put it simply, lvalues
are values assigned to variables, and rvalues
are not.
int x = 1;
std::cout << x << std::endl;
std::cout << 2 << std::endl;
std::function<void()> f = []{ std::cout << 3 << std::endl; };
scope_exit_t y = f;
scope_exit_t z = []{ std::cout << 4 << std::endl; };
The example tells us that we should reject lvalues
to get to our goal. It became possible by the C++0x feature called "Rvalue
Reference".
#include <iostream>
void func(int &x) { std::cout << "Called by lvalue: x == " << x << std::endl; };
void func(int &&x) { std::cout << "Called by rvalue: x == " << x << std::endl; };
int main()
{
int x = 1;
func(x); func(2);
return 0;
}
In this code, there are two overloads of func()
. The second one may be unfamiliar to you. It takes an rvalue
reference. It’s the hottest topic among the C++ programmers now, but this article is not to take part in them. It's just one point I want you to notice that which overload will be called is determined by whether the parameter is lvalue
or rvalue
. It allows functions to judge which kind of value they received. Therefore, we should make the public
constructor take rvalue
, and the private
one take lvalue
. scope_exit_t
can no longer be constructed from function objects assigned to a variable.
public:
scope_exit_t(func_t &&f) : func(f) {} private:
scope_exit_t(func_t &);
Hide behind the Macro
The variables for the scope_exit_t
class require the unique names in some cases. But we don't need to give them the names each time. We can leave the job to the preprocessor. The predefined macro __COUNTER__ lets us generate unique variable names. (Though it's not standard, it's available for g++ too.) But we need a small trick (but common practice) to use it properly. This shows you the practice.
#define SCOPE_EXIT_CAT2(x, y) x##y
#define SCOPE_EXIT_CAT1(x, y) SCOPE_EXIT_CAT2(x, y)
#define SCOPE_EXIT scope_exit_t SCOPE_EXIT_CAT1(scope_exit_, __COUNTER__) = [&]
In this code, SCOPE_EXIT_CAT1
looks like doing nothing, but plays an important role. __COUNTER__
won't be replaced with a number if this code lacks SCOPE_EXIT_CAT1
, because __COUNTER__
is joined to scope_exit_
before being replaced. SCOPE_EXIT_CAT1
is necessary to delay token concatenation.
(If you can use the Boost, it's easier to use BOOST_PP_CAT to concatenate tokens.)
History
- 3 Nov, 2010: Initial post
- 3 June, 2014: Added the new version to the source archive.
I don't have time to discuss the new version right now. I'll do it later on.