Click here to Skip to main content
15,867,771 members
Articles / Programming Languages / Visual C++ 12.0

Simple-Look Scope Guard for Visual C++ 2010

Rate me:
Please Sign up or sign in to vote.
4.89/5 (42 votes)
18 Jun 2014Public Domain5 min read 61.8K   524   59   11
Implement a simple-look substitution of BOOST_SCOPE_EXIT using some of the new features of C++0x

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.

C++
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(); };

    // Some work.

    return 0;

    // Executed in the reverse order. 
    //   1. _Module.Term();
    //   2. Gdiplus::GdiplusShutdown(gdipToken);
    //   3. ::CoUninitialize();
}

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.

C++
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;
    
    // Some work.

    return 0;

    // Executed in the reverse order. 
    //   1. _Module.Term();
    //   2. Gdiplus::GdiplusShutdown(gdipToken);
    //   3. ::CoUninitialize();
}

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:

C++
#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;
}

// Output:
//  Resource Allocation.
//  Some Work.
//  Resource Deallocation

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:

C++
int main()
{
    int *p = new int[10];
    scope_exit_t x = [&]{ delete[] p; };
    scope_exit_t y = x;

    // Some work.

    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 =.

C++
// Inside the scope_exit_t
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.

C++
int main()
{   
    scope_exit_t *p = new scope_exit_t([]{ std::cout << 1 << std::endl; });  

    // Some work.

    delete p; // You should never skip it!

    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.

C++
// Inside the scope_exit_t
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:

C++
int main()
{
    std::function<void()> f = []{ std::cout << 1 << std::endl; }; // Function 1.
    scope_exit_t x = f;

    f = []{ std::cout << 2 << std::endl; }; // Function 2.

    return 0;
}

// Output:
//  1

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...

C++
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.

C++
// 1: The integer is lvalue. It's assigned to a variable.
int x = 1;
std::cout << x << std::endl;

// 2: The integer is rvalue. It's not assigned to any variables.
std::cout << 2 << std::endl;

// 3: The lambda expression is lvalue. It's assigned to a variable.
std::function<void()> f = []{ std::cout << 3 << std::endl; };
scope_exit_t y = f;

// 4: The lambda expression is rvalue. It's not assigned to any variables.
//    It looks like assigned to z, but it's directly passed 
//    to the constructor of scope_exit_t.
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".

C++
#include <iostream>

// Takes lvalue references (the argument has one ampersand.)
void func(int &x) { std::cout << "Called by lvalue: x == " << x << std::endl; };

// Takes rvalue references (the argument has two ampersands.)
void func(int &&x) { std::cout << "Called by rvalue: x == " << x << std::endl; };

int main()
{
    int x = 1;
    func(x); // Call by lvalue.
    func(2); // Call by rvalue.

    return 0;
}

// Output:
//  Called by lvalue: x == 1
//  Called by rvalue: x == 2

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.

C++
// Inside the scope_exit_t
public:
    scope_exit_t(func_t &&f) : func(f) {}  // Accept rvalue references.
private:
    scope_exit_t(func_t &);                // Reject lvalue references.

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.

C++
#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.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer
Japan Japan
In 1985, I got my first computer Casio MX-10, the cheapest one of MSX home computers. Then I began programming in BASIC and assembly language, and have experienced over ten languages from that time on.
Now, my primary languages are C++ and C#. Working for a small company in my home town in a rural area of Japan.


Comments and Discussions

 
GeneralMy vote of 5 Pin
Cristian Amarie17-Jun-14 5:24
Cristian Amarie17-Jun-14 5:24 
SuggestionN3949... Pin
liahona2-May-14 8:13
liahona2-May-14 8:13 
GeneralRe: N3949... Pin
Tsuda Kageyu2-May-14 11:41
Tsuda Kageyu2-May-14 11:41 
GeneralMy vote of 5 Pin
Sergey Podobry18-May-12 3:55
professionalSergey Podobry18-May-12 3:55 
GeneralAmazing Pin
Ajay Vijayvargiya16-Nov-10 6:47
Ajay Vijayvargiya16-Nov-10 6:47 
GeneralThank you for your careful reviewing Pin
Tsuda Kageyu16-Nov-10 10:34
Tsuda Kageyu16-Nov-10 10:34 
GeneralMy vote of 5 Pin
S.H.Bouwhuis16-Nov-10 1:21
S.H.Bouwhuis16-Nov-10 1:21 
GeneralRe: My vote of 5 Pin
.:floyd:.24-Apr-14 23:40
.:floyd:.24-Apr-14 23:40 
GeneralRe: My vote of 5 Pin
S.H.Bouwhuis24-Apr-14 23:47
S.H.Bouwhuis24-Apr-14 23:47 
GeneralSCOPE_EXIT calling order Pin
owillebo9-Nov-10 1:37
owillebo9-Nov-10 1:37 
GeneralRe: SCOPE_EXIT calling order Pin
Tsuda Kageyu9-Nov-10 2:15
Tsuda Kageyu9-Nov-10 2:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.