Proposal: Generic RAII library...

1,099 views
Skip to first unread message

Andrew Sandoval

unread,
Nov 3, 2012, 10:32:25 PM11/3/12
to std-pr...@isocpp.org
I'd like to propose the addition of generic template classes for RAII to the Standard Library.  shared_ptr, and unique_ptr are great for pointers and things that act and feel like pointers, but not are not a perfect fit for many things.  In general I would like to see the following:

1) A class that cleans up resources or runs any type of a function or lambda on scope-exit.  I've posted an example of such a class on the boost list, that is a simple composition using std::tr1::function<T ()>, a copy of which is below (RAIIFunction).  The problem, as was pointed out on the boost list, is that construction of a std::tr1::function object can throw in which case RAII behavior would not be achieved.

I'd like to know if that issue could be addressed (maybe a compile time instead of runtime) so that an equivalent to RAIIFunction could always be depended upon?

2) A class that wraps any type of an object provided there is a "deleter" function that takes a single argument of the type being deleted, and that there is a static no-delete value.  This class should also be entirely, or almost entirely static so that it's overhead in generated code is no more than the C-style goto ErrorExit: with if(resource) { cleanup(resource); } semantics.  An example of this is provided below, called RAIIWrapper.  It works with Visual Studio 2008 through 2012, but I've not tried other compilers.

I believe that providing these types of classes in the standard library along side of shared_ptr, unique_ptr, etc., will help C++ engineers to produce higher quality code with fewer resource leaks and unwind problems.  More importantly, I believe the once developers latch on to the concept of RAII they are more likely to be careful in planning resource lifetime -- a source of far too many bugs.  I see RAII as a huge paradigm change that leads to higher quality code in general.

I've written and rewritten classes like the following many times for different projects -- in fact, it seems to be something I have to rewrite for every new product or project.  My goal isn't to push these classes in particular, or their style, or especially their names (nomenclature isn't my specialty), but to see something very similar in the C++ standard library -- so that I don't have to keep rewriting them, and so that others can benefit from them too!

Thanks Much,
Andrew Sandoval

NOTE: This code was written for Windows, so some things would need #ifdef's for other systems.  It's provided just for example purposes!  (And the example usage is a bit contrived in places, but shows possibilities.)

//
// RAII Wrappers - Written by Andrew L. Sandoval 2011/2012
#pragma once
#include <functional>

//
// Some classes for identifying function prototypes:
//

class cStdcall
{
};

class cFastcall
{
};

class cCdecl
{
};

class c64Bit
{
};

//
// Clean-up stuff -- remove const and reference from a type
//
template <typename T>
struct StripRefOrConst
{
    typedef T type;
};

template <typename T>
struct StripRefOrConst<const T>
{
    typedef typename StripRefOrConst<T>::type type;
};

template <typename T>
struct StripRefOrConst<const T&>
{
    typedef typename StripRefOrConst<T>::type type;
};

//
// Extract First Parameter Type and Calling Convention
//
template <typename T>
struct FuncInfo1;

#ifdef _WIN64
template <typename TReturn, typename TParam>
struct FuncInfo1<TReturn (*)(TParam)>
{
    typedef TReturn tReturnType;
    typedef TParam tParameter1;
    typedef c64Bit tBaseClassType;
};
#else // 32-bit
template <typename TReturn, typename TParam>
struct FuncInfo1<TReturn (__stdcall *)(TParam)>
{
    typedef TReturn tReturnType;
    typedef TParam tParameter1;
    typedef cStdcall tBaseClassType;
};

template <typename TReturn, typename TParam>
struct FuncInfo1<TReturn (__cdecl *)(TParam)>
{
    typedef TReturn tReturnType;
    typedef TParam tParameter1;
    typedef cCdecl tBaseClassType;
};

template <typename TReturn, typename TParam>
struct FuncInfo1<TReturn (__fastcall *)(TParam)>
{
    typedef TReturn tReturnType;
    typedef TParam tParameter1;
    typedef cFastcall tBaseClassType;
};
#endif    // _WIN64

//
// Now an RAII class that is tight and static where possible
//
template <typename T, typename TDeleterFunc, TDeleterFunc tpfnDelete, T tNoDeleteValue = NULL>  // nullptr can NOT be used here!
class RAIIWrapperBase
{
protected:
    T m_resource;
    RAIIWrapperBase();
    explicit RAIIWrapperBase(const RAIIWrapperBase &);
    void operator=(const RAIIWrapperBase &);
public:
    typedef T T;
    typedef TDeleterFunc TDeleterFunc;

    RAIIWrapperBase(T resource) : m_resource(resource)
    {
    }

    bool InvokeAndRelease()    // May be called directly to release early
    {
        if(tNoDeleteValue != m_resource)
        {
            tpfnDelete(m_resource);
            m_resource = tNoDeleteValue;
            return true;
        }
        return false;
    }

    ~RAIIWrapperBase()
    {
        InvokeAndRelease();
    }

    RAIIWrapperBase& Cancel()    // No longer needs to be deleted
    {
        m_resource = tNoDeleteValue;
        return *this;
    }

    T& operator&()
    {
        return &m_resource;
    }

    T Get() const
    {
        return m_resource;
    }

    operator T() const
    {
        return m_resource;
    }

    bool operator=(T resource)
    {
        bool b = InvokeAndRelease();
        m_resource = resource;
        return b;
    }
};

//
// RAIIWrapper invokes RAIIWrapperBase simplified down to <decltype(&deleterFn), deleterFn, NoDeleteValue>
//
template<typename TDeleterFunc, TDeleterFunc tpfnDelete, typename StripRefOrConst<typename FuncInfo1<TDeleterFunc>::tParameter1>::type tNoDeleteValue = NULL>
class RAIIWrapper : public RAIIWrapperBase<typename StripRefOrConst<typename FuncInfo1<TDeleterFunc>::tParameter1>::type, TDeleterFunc, tpfnDelete, tNoDeleteValue>
{
private:
    explicit RAIIWrapper(const RAIIWrapper &);
    void operator=(const RAIIWrapper &);
public:
    //
    // Empty constructor is possible to allow operator= later
    RAIIWrapper() : RAIIWrapperBase(tNoDeleteValue)
    {
    }

    RAIIWrapper(const T &resource) : RAIIWrapperBase(resource)
    {
    }

    void operator=(T resource)
    {
        RAIIWrapperBase::operator=(resource);
    }

    bool IsValid() const
    {
        return m_resource != tNoDeleteValue;
    }
};

//
// Simplify usage with a macro:
#define RAIIDeleter(deleterFunction) decltype(&deleterFunction), deleterFunction

//
// EXAMPLES:
// 1: (with non-default no-delete value)
// RAIIWrapper<RAIIDeleter(CloseHandle), INVALID_HANDLE_VALUE> hOutput = CreateFileA(pszDatabasePath,
//         GENERIC_WRITE,
//         FILE_SHARE_WRITE,
//         NULL, CREATE_ALWAYS,
//         FILE_ATTRIBUTE_NORMAL,
//         NULL);
//     if(!hOutput.IsValid())
//     {
//         DWORD dwLastErr = GetLastError();
//         pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_ERROR,
//             "Error <link cmd=\"!error %N\" %N creating %hs\n", dwLastErr, dwLastErr, pszDatabasePath);
//         return false;
//     }
//
// 2: (Using default no-delete value of NULL)
// RAIIWrapper<RAIIDeleter(sqlite3_close)> m_pDatabase;
// m_pDatabase(NULL) in the class init-list, or filled in with an actual value by constructor, etc...
//
// 3: (Easier still)
// RAIIWrapper<RAIIDeleter(CloseHandle)> hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// .
// .
// SetEvent(hEvent);  // Maybe hEvent was passed to something else doing a WFSO, etc.

////////////////////////////////

//
// An RAII class that works with std::tr1::bind and lambdas
// NOTE: making a const RAIIFunction prevents the use of Invoke() and Cancel() and operator=(fn) which is often appropriate
// NOTE: m_func copies the original function object, so that const and non-const refs can be passed in, etc.
// WARNING: std::tr1::bind can throw / fail, so also can the std::tr1::function constructor!
template <typename TRet = void>
class RAIIFunction
{
private:
    std::tr1::function<TRet ()> m_func;
    bool m_bInvoke;

    RAIIFunction();
    RAIIFunction(const RAIIFunction &);
    void operator=(const RAIIFunction &);
public:
    RAIIFunction(const std::tr1::function<TRet ()> &fn) : m_func(fn), m_bInvoke(true)
    {
    }

    void operator=(const std::tr1::function<TRet ()> &fn)
    {
        if(m_bInvoke)
        {
            m_func();
        }
        m_bInvoke = true;
        m_func = fn;
    }

    ~RAIIFunction()
    {
        if(m_bInvoke)
        {
            m_func();
        }
    }

    void Cancel()
    {
        m_bInvoke = false;
    }

    TRet Invoke(bool bRelease = false)
    {
        m_bInvoke = !bRelease;
        return m_func();
    }
};

//
// Examples:
// 1: Simple, would be tighter using RAIIWrapper above, but for example sake...
// RAIIFunction<void> closeDB = ([&pDB]()
// {
//        if(pDB)
//        {
//            sqlite3_close(pDB);
//        }
// });
//
// 2: Using bind
// RAIIFunction<int> mb = std::tr1::bind(MessageBoxW, HWND_TOP, L"Test", L"Test", MB_TOPMOST);
// int iMB = mb.Invoke(true);
//
// 3: const example with logic, still RAIIWrapper is better for this!
// const RAIIFunction<void> closeDB = ([&pDB]()
// {
//        if(pDB)
//        {
//            sqlite3_close(pDB);
//        }
// });
//
//4: bind with early invocation, then being "reset" to a new lambda prior to scope-exit...
// RAIIFunction<int> mb = std::tr1::bind(MessageBoxW, HWND_TOP, L"Test", L"Test", MB_TOPMOST);
// int iMB = mb.Invoke(true);
// mb = ([]() ->int
// {
//     printf("test\n");
//     return 2;
// });



DeadMG

unread,
Nov 4, 2012, 8:31:06 AM11/4/12
to std-pr...@isocpp.org
HANDLEs are void*, so you can do
std::unique_ptr<void, decltype(&CloseHandle)> p(CreateFile(...), &CloseHandle);
But more accurately, you can alter the type of the pointer to anything you want through the deleter's pointer typedef. 

Your example of the RAIIWrapper is bad. constexpr function pointers only? The Committee would never accept that in an interface. Not only is it hideously ugly, as is the macro you used to try and simplify it, but it's also exceedingly limiting- far more than unique_ptr. If unique_ptr can't accept the whole range of resources you want, then consider proposing that it's range be expanded by operating in a somewhat different manner for non-pointer types, rather than a duplicate class which, frankly, is much worse in basically every other respect.

As for RAIIFunction, it's not functionality I use often, so I can't comment too much on the need for such a class, but you could start by altering the interface so that it works with the rest of the Standard library, at least.

Andrew Sandoval

unread,
Nov 4, 2012, 3:59:51 PM11/4/12
to std-pr...@isocpp.org
First, I'm well aware of what a HANDLE is, and that it can be used with std::unique_ptr if it's value is not INVALID_HANDLE_VALUE (-1).  That is why RAIIWrapper allows for a no-delete value.  If you can accomplish the same thing with std::unique_ptr, I'm all ears.

Second, RAIIWrapper produces very tight code -- I think I pointed that out, and it is because the function is a "constexpr" as you put it..  The environment in which I first used it had that requirement, as code size was almost of paramount importance.  In many cases unique_ptr would be fine, but I believe that there is a legitimate place for constexpr function pointers here.  You'll notice I didn't see it was the best or only option for RAII, but it has a place.

You said "The Committee would never accept that in an interface. Not only is it hideously ugly, as is the macro you used to try and simplify it, but it's also exceedingly limiting- far more than unique_ptr."  Can you please elaborate so that those of us that aren't as smart understand why this is a such a problem that the committee would snub their noses at the thought?  I'm seriously interested, because I know I am far more pragmatic than I am intellectual about things like this.

I personally have a distaste for macros, but the only use for RAIIDeleter is to hide the additional template parameter which will always be the same as the first parameter only wrapped in declytpe.  Nobody would force the use of such a macro I think. :)

I'm frankly happy to learn more.  I'm sure you didn't mean to sound as harsh as you did.  I'm suggesting adding this type of functionality to the standard library, but if there is a way to accomplish my goals using existing libraries, I'm happy to learn!

So, for example, let's say it's this code:

int fd = open(path, O_RDONLY);
.
.
.
close(fd);


1) How do you do that with std::unique_ptr?
and
2) How tight is the code (assembly code) it produces compared to an RAIIWrapper?

As for RAIIFunction...  One of my goals is to use RAII in all of the places that common mistakes are made that could be solved using it.  So, sometimes I'll even use it inside of a while loop to ensure that anyone that comes in later on and modifies that code can't accidentally forget to advance the loop, because at the top of the loop is an RAIIFunction object that ensures that any continue will advance the loop, etc.

It is also good for all of the things people used to use gotos for, or that they used to have complex nesting for...  If you have any block of code that needs to always -- or even just usually execute before leaving a particular scope, you can put that block in a lambda and assign it to an RAIIWrapper.  The result is typically flatter functions/methods (less nesting), and simplified logic.

Thanks.  I'm honestly not proposing this because I view myself in any way a C++ genius.  I appreciate anyone willing to take the time to promote C++ and especially those who wish to improve the quality of C++ code.  I just ask that rather than responding with a jeer (if that was intended), look for a way to genuinely educate.

Thank you,
Andrew Sandoval

Ville Voutilainen

unread,
Nov 4, 2012, 4:24:23 PM11/4/12
to std-pr...@isocpp.org
On 4 November 2012 22:59, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> int fd = open(path, O_RDONLY);
> .
> .
> .
> close(fd);
> 1) How do you do that with std::unique_ptr?
> and
> 2) How tight is the code (assembly code) it produces compared to an
> RAIIWrapper?


struct handle_deleter
{
void operator()(int* p)
{
close(*p);
}
};
int x = open(path, O_RDONLY);
unique_ptr<int, handle_deleter> phoo{&x};

Not as RAII-ish as one might want. I expect the code to be rather "tight".

> As for RAIIFunction... One of my goals is to use RAII in all of the places

The idea seems reasonable. The standard smart pointers insist on receiving
a pointer, and that yields to a temporary in at least my example case.
A solution
that can build a RAII handle over non-pointers seems like a decent idea.

Howard Hinnant

unread,
Nov 4, 2012, 4:32:03 PM11/4/12
to std-pr...@isocpp.org
On Nov 4, 2012, at 4:24 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

> On 4 November 2012 22:59, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
>> int fd = open(path, O_RDONLY);
>> .
>> .
>> .
>> close(fd);
>> 1) How do you do that with std::unique_ptr?
>> and
>> 2) How tight is the code (assembly code) it produces compared to an
>> RAIIWrapper?
>
>
> struct handle_deleter
> {
> void operator()(int* p)
> {
> close(*p);
> }
> };
> int x = open(path, O_RDONLY);
> unique_ptr<int, handle_deleter> phoo{&x};
>
> Not as RAII-ish as one might want. I expect the code to be rather "tight".

I got it down to this:

int fd = open(path, O_RDONLY);
std::unique_ptr<int, void(*)(int*)> closer(&fd, [](int* fd) {close(*fd);});

That's not meant to be a statement for or against a generic RAII library. I just had a moment and thought I would experiment. The assembly (clang++ -O3 x86_64) got down to:

...
callq _open
Ltmp0:
movl %eax, %edi
callq _close
...

With of course an exception handling table to handle the exceptional case.

Howard

Ville Voutilainen

unread,
Nov 4, 2012, 4:40:32 PM11/4/12
to std-pr...@isocpp.org
On 4 November 2012 23:32, Howard Hinnant <howard....@gmail.com> wrote:
> On Nov 4, 2012, at 4:24 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
> I got it down to this:
> int fd = open(path, O_RDONLY);
> std::unique_ptr<int, void(*)(int*)> closer(&fd, [](int* fd) {close(*fd);});

Some of us can't get their head around the fact that non-capturing
lambdas convert to a function
pointer. ;)

However, there's still a problem there; the resource acquired doesn't
initialize the allegedly RAII
object which is the unique_ptr. That's why I still see reasons to
strive for a generic non-pointer handle.
Being able to pass the resource directly into the handle and giving a
lambda as a deleter would
seem like a decent solution, especially if we can find a reasonable
default for the deleter, which we
likely can find. Perhaps a default_nonptr_delete that can be
specialized. (side question:
can I specialize default_delete for MyType*?)

Howard Hinnant

unread,
Nov 4, 2012, 4:46:02 PM11/4/12
to std-pr...@isocpp.org
On Nov 4, 2012, at 4:40 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

> side question:
> can I specialize default_delete for MyType*?

As far as I know, yes, as long as you meet the requirements of [unique.ptr.dltr.dflt].

Howard

Ville Voutilainen

unread,
Nov 4, 2012, 4:48:42 PM11/4/12
to std-pr...@isocpp.org
Then again, I don't think mirroring default_delete is a good idea,
since I at least
have no clue what this mystical default_nonptr_delete would do. :)
Perhaps defaulting
the deleter type to a function pointer would be a better solution
after all, that doesn't
prevent using functors where necessary.

Daniel Krügler

unread,
Nov 4, 2012, 4:50:36 PM11/4/12
to std-pr...@isocpp.org
2012/11/4 Ville Voutilainen <ville.vo...@gmail.com>

(side question:
can I specialize default_delete for MyType*?)


std::default_delete doesn't give special guarantees, so this is ruled
by the general requirements in Clause 17, notably 17.6.4.2.1 p1.

(I guess you meant to specialize the class template for MyType. The standard
doesn't give you grants to explicitly specialize member functions or member
function templates of library templates).

- Daniel

Daniel Krügler

unread,
Nov 4, 2012, 5:01:08 PM11/4/12
to std-pr...@isocpp.org
2012/11/4 Ville Voutilainen <ville.vo...@gmail.com>

Then again, I don't think mirroring default_delete is a good idea,
since I at least
have no clue what this mystical default_nonptr_delete would do. :)
Perhaps defaulting
the deleter type to a function pointer would be a better solution
after all, that doesn't
prevent using functors where necessary.


I don't see that this would improve anything, you just change the default
and your argument could easily be inverted to say "that doesn't prevent using
function pointers where necessary".

Furthermore I think that changing the default here (irrespective of the usually
bad idea to change a default at all) in the sense you seem to suggest is
not an improvement: It does conflict with all other places in the library that take
functors (e.g. maps), so why should unique_ptr invent a new style? Another
argument against this change is that people would believe they could
simply use these C-like functions (like close) which typically have C linkage
as valid argument. But this would only be possible for systems that let
extern "C" functions be compatible to extern "C++" functions. Albeit many
implementations allow this (in violation of the standard, see
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1555), this
is not conforming.

I think the current default is exactly right.

- Daniel



Ville Voutilainen

unread,
Nov 4, 2012, 5:04:56 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 00:01, Daniel Krügler <daniel....@gmail.com> wrote:
> Furthermore I think that changing the default here (irrespective of the
> usually
> bad idea to change a default at all) in the sense you seem to suggest is
> not an improvement: It does conflict with all other places in the library
> that take
> functors (e.g. maps), so why should unique_ptr invent a new style? Another

unique_ptr? Whoa, we're not talking about changing unique_ptr. :)

> argument against this change is that people would believe they could
> simply use these C-like functions (like close) which typically have C
> linkage
> as valid argument. But this would only be possible for systems that let
> extern "C" functions be compatible to extern "C++" functions. Albeit many
> implementations allow this (in violation of the standard, see
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1555), this
> is not conforming.

Ouch. A relatively common case for such a unique_handle would be to use
such functions for the cleanup. It's unfortunate to have to wrap into a lambda.

> I think the current default is exactly right.

Fair enough.

DeadMG

unread,
Nov 4, 2012, 5:44:53 PM11/4/12
to std-pr...@isocpp.org
I didn't mean to sound like a jerk, and I didn't mean to jeer at you. Silly Google Groups ate my original reply, so let me be brief.

Your need for very tight binary code is just that, your need. The Standard needs to consider everyone's needs. Your need is very niche, and very specialist. There's no reason the Committee would severely impair a general-purpose interface which works fine for virtually everybody for the vast minority. It's much better to ask you to write a new class for your specific need than to ask everybody to write a new class. They already considered the impact of unique_ptr's interface, and decided on it. It shouldn't yield any worse code size than your RAIIWrapper, and if it did, then call your compiler vendor- there's nothing the Committee can do about poor QoI.

As for non-pointer resources, I believe it should be perfectly possible to use unique_ptr with them. If not, consider proposing an extension to it that would change this. Introducing a second class for the same purpose- unique ownership of a resource- is not the solution. After all, what next, another new class for shared ownership?

As for RAIIFunction, you should at least start by converting it's interface to mirror that of the Standard, and propose an interface for it which matches that of the normal Standard components, like std::scope_exit or something. 

Ville Voutilainen

unread,
Nov 4, 2012, 5:52:40 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 00:44, DeadMG <wolfei...@gmail.com> wrote:
> I didn't mean to sound like a jerk, and I didn't mean to jeer at you. Silly
> Google Groups ate my original reply, so let me be brief.
> Your need for very tight binary code is just that, your need. The Standard
> needs to consider everyone's needs. Your need is very niche, and very

I haven't yet found people who need bloated code.

> specialist. There's no reason the Committee would severely impair a
> general-purpose interface which works fine for virtually everybody for the
> vast minority. It's much better to ask you to write a new class for your
> specific need than to ask everybody to write a new class. They already
> considered the impact of unique_ptr's interface, and decided on it. It
> shouldn't yield any worse code size than your RAIIWrapper, and if it did,
> then call your compiler vendor- there's nothing the Committee can do about
> poor QoI.

The committee solved the more common problem with smart pointers. The
committee didn't have time to solve the generic resource handle problem.
Variadic templates and lambdas give us good tools to do that, though.

> As for non-pointer resources, I believe it should be perfectly possible to
> use unique_ptr with them. If not, consider proposing an extension to it that

Except that it won't be quite RAII. See the previous posts.

> would change this. Introducing a second class for the same purpose- unique
> ownership of a resource- is not the solution. After all, what next, another

I think it is exactly the solution, rather than trying to shoehorn
non-pointers into
smart pointers.

> new class for shared ownership?

Yes, maybe. We shouldn't limit ourselves into the existing classes of
c++11 if it
becomes apparent that a new class is the right way to go.

DeadMG

unread,
Nov 4, 2012, 6:05:49 PM11/4/12
to std-pr...@isocpp.org
> I haven't yet found people who need bloated code.

They need restricted interfaces a lot less than they need a few extra KB. Nor do you present any evidence that std::unique_ptr's interface produces larger code size on any major compiler- or how much that would be.

> The committee solved the more common problem with smart pointers. The 
> committee didn't have time to solve the generic resource handle problem. 
> Variadic templates and lambdas give us good tools to do that, though. 

There's a reason why the deleter's pointer typedef and that abstraction exists, and it's to deal with generic resource handles. If this support is deficient, then that's a defect.

> Except that it won't be quite RAII. See the previous posts.

I have read them and I'm really not sure how it wouldn't qualify as RAII. There's no difference between

std::unique_ptr<int, decltype(&close)> ptr(open(...), &close);

and

std::unique_ptr<T*, std::default_deleter<T*>> ptr(new T, std::default_deleter<T>());

>I think it is exactly the solution, rather than trying to shoehorn non-pointers into smart pointers.

Smart pointers handle resources. The fact that not all resources are pointers simply means they should be more generic, if they cannot handle non-pointer resources. There's no reason to introduce two different resource-handling classes just because some resources are pointers and some are not, given that a slight abstraction can easily handle the differences between them.

Ville Voutilainen

unread,
Nov 4, 2012, 6:10:47 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 01:05, DeadMG <wolfei...@gmail.com> wrote:
>> I haven't yet found people who need bloated code.
> They need restricted interfaces a lot less than they need a few extra KB.
> Nor do you present any evidence that std::unique_ptr's interface produces
> larger code size on any major compiler- or how much that would be.

It doesn't. unique_ptr has very small overhead, if any. The same is desired
for a unique_handle, and unique_ptr's interface doesn't give that RAII behavior
for non-pointers.

>> Except that it won't be quite RAII. See the previous posts.
> I have read them and I'm really not sure how it wouldn't qualify as RAII.
> There's no difference between
> std::unique_ptr<int, decltype(&close)> ptr(open(...), &close);
> and
> std::unique_ptr<T*, std::default_deleter<T*>> ptr(new T,
> std::default_deleter<T>());

Except that you can't do the former.

> Smart pointers handle resources. The fact that not all resources are
> pointers simply means they should be more generic, if they cannot handle
> non-pointer resources. There's no reason to introduce two different
> resource-handling classes just because some resources are pointers and some
> are not, given that a slight abstraction can easily handle the differences
> between them.

That slight abstraction seems unattainable for the smart pointers.
Whether it's attainable
for a new resource handle type is another matter.

There's already multiple resource handling classes: containers,
streams, lock wrappers,
multiple smart pointers, etc. They are there because they do different things.

Howard Hinnant

unread,
Nov 4, 2012, 6:19:59 PM11/4/12
to std-pr...@isocpp.org

On Nov 4, 2012, at 6:10 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

> On 5 November 2012 01:05, DeadMG <wolfei...@gmail.com> wrote:
>>> I haven't yet found people who need bloated code.
>> They need restricted interfaces a lot less than they need a few extra KB.
>> Nor do you present any evidence that std::unique_ptr's interface produces
>> larger code size on any major compiler- or how much that would be.
>
> It doesn't. unique_ptr has very small overhead, if any. The same is desired
> for a unique_handle, and unique_ptr's interface doesn't give that RAII behavior
> for non-pointers.
>
>>> Except that it won't be quite RAII. See the previous posts.
>> I have read them and I'm really not sure how it wouldn't qualify as RAII.
>> There's no difference between
>> std::unique_ptr<int, decltype(&close)> ptr(open(...), &close);
>> and
>> std::unique_ptr<T*, std::default_deleter<T*>> ptr(new T,
>> std::default_deleter<T>());
>
> Except that you can't do the former.

This works:

#include <memory>
#include <fcntl.h>
#include <unistd.h>

struct file_descriptor
{
typedef int pointer;

void operator()(pointer fd)
{close(fd);}
};

void test1()
{
const char* path = "test.cpp";
std::unique_ptr<int, file_descriptor> fd(open(path, O_RDONLY));
char buf[10];
read(fd.get(), buf, sizeof(buf));
}

No opinion on whether this is the best way or not. Just saying it works.

Howard

DeadMG

unread,
Nov 4, 2012, 6:23:18 PM11/4/12
to std-pr...@isocpp.org
More generally, I also devised 

#include <memory>

int open() { return 1; }
void close(int) {}

template<typename T, typename deleter, deleter d, T null_value = T {}> struct value_deleter {
   
struct wrapper {
        T value
;
        wrapper
(T t) : value(t) {}
       
bool operator==(std::nullptr_t) { return value == null_value; }
       
bool operator!=(std::nullptr_t) { return value != null_value; }
        T
operator*() { return value; }
        wrapper
(std::nullptr_t) : value(null_value) {}
        wrapper
() : value(null_value) {}
   
};
   
typedef wrapper pointer;
   
void operator()(wrapper w) {
       
if (w.value != null_value)
            d
(w.value);
   
}
};
int main() {
    std
::unique_ptr<int, value_deleter<int, decltype(&close), &close>> ptr(open());
}

This compiles fine with GCC. I see no problem here.

Ville Voutilainen

unread,
Nov 4, 2012, 6:37:06 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 01:23, DeadMG <wolfei...@gmail.com> wrote:
> More generally, I also devised
> This compiles fine with GCC. I see no problem here.

Yes. Books are full of such work-arounds. I can just as well write my
own wrapper and not
use unique_ptr at all, which is not what the original idea is after.
It's trying to provide a simple
utility that avoids having to write that much wrapper code.

Nicol Bolas

unread,
Nov 4, 2012, 6:37:55 PM11/4/12
to std-pr...@isocpp.org
This is all very great and clever and everything, but I would like to remind you of one thing.

It's called unique_ptr, not unique_handle, unique_reference, or unique_object. It feels very... wrong to be sticking non-pointer things in something called unique_ptr.

I'm not saying that it's wrong or somehow worse than what he proposed. But it's not exactly obvious that unique_ptr should or even could be used that way.

Of course, this could be settled by doing this:

template<typename T, typename CloseType>
using unique_handle<T> = std::unique_ptr<T, value_delete<T, CloseType>>

Obviously value_delete would no longer be able to have the deletion function as a template parameter.

Perhaps this should be added to the standard library. Along with a `make_handle` function that takes a handle value and a deletion function.

DeadMG

unread,
Nov 4, 2012, 6:40:21 PM11/4/12
to std-pr...@isocpp.org
You make it sound like the RAIIWrapper presented in the OP was not vastly more code than my about fifteen-line wrapper.

Nicol, I don't think that the name is quite so important. A file descriptor is a pointer, it just has an unusual implementation.

Ville Voutilainen

unread,
Nov 4, 2012, 6:41:40 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 01:40, DeadMG <wolfei...@gmail.com> wrote:
> You make it sound like the RAIIWrapper presented in the OP was not vastly
> more code than my about fifteen-line wrapper.

It can be written much shorter. Just because there is a naive
prototype implementation
doesn't mean the idea isn't sound. And furthermore, I don't have to
know how many
lines the standard facility consists of when I'm using it.

DeadMG

unread,
Nov 4, 2012, 6:45:55 PM11/4/12
to std-pr...@isocpp.org
No, but you are asking the Standard library to duplicate a bunch of code and functionality, and expend a lot of valuable Committee time, when the existing abstraction can handle the problem just fine. Adding things to the Standard is a lot more expensive than the quick wrapper I belted out in five minutes. It takes a whole lot more than that. Every second the Committee spends debating this class, they could be spending on concepts, or modules, or something like that, which would deliver vastly more benefit. The time of the Committee is far more valuable than the few minutes it takes to use this particular abstraction.

Ville Voutilainen

unread,
Nov 4, 2012, 6:48:50 PM11/4/12
to std-pr...@isocpp.org
On 5 November 2012 01:45, DeadMG <wolfei...@gmail.com> wrote:
> No, but you are asking the Standard library to duplicate a bunch of code and
> functionality, and expend a lot of valuable Committee time, when the
> existing abstraction can handle the problem just fine. Adding things to the

It's the "just fine" part we apparently disagree on. Have a nice day.

Nicol Bolas

unread,
Nov 4, 2012, 6:55:08 PM11/4/12
to std-pr...@isocpp.org


On Sunday, November 4, 2012 3:45:55 PM UTC-8, DeadMG wrote:
No, but you are asking the Standard library to duplicate a bunch of code and functionality, and expend a lot of valuable Committee time, when the existing abstraction can handle the problem just fine. Adding things to the Standard is a lot more expensive than the quick wrapper I belted out in five minutes. It takes a whole lot more than that. Every second the Committee spends debating this class, they could be spending on concepts, or modules, or something like that, which would deliver vastly more benefit. The time of the Committee is far more valuable than the few minutes it takes to use this particular abstraction.

By that logic, why add std::string to the standard? It's a simple enough class, and anyone could come up with a passable facsimile in a few minutes.

The reasons for putting things in the standard are:

1: Reuse. If everyone is writing the same class, then obviously they're wasting valuable time. Let it be centralized, so that it can be properly tested.

2: Comprehension. Everyone does it the same way. Because of this, it makes it more clear as to what's going on. If I see someone pass a `unique_handle<int, deleter>` around, it's very clear what they're doing. When I see `unique_ptr<int, big_giant<template, with, many, arguments>>`, I have no intuitive understanding of what that means. It looks like a big, giant hack (which is what it is).

3: Functionality and interoperability. Everyone could write their own std::string, but they would have different behaviors. By putting it in the standard, everyone uses the same string class (theoretically), so they can inter-operate. Plus, there's a specific standard of behavior one gets out of strings.

If there really is a problem with the standards committee handling minor proposals, then that's a problem that should be fixed, not ignored. I think there should be a study group or some provision for minor proposals like this, so that there don't have to be big arguments or whatever about them. They're not important enough to debate that much, but it's important for the standard to provide appropriate behavior.

DeadMG

unread,
Nov 4, 2012, 7:04:48 PM11/4/12
to std-pr...@isocpp.org
The problem of not introducing std::string is quite a bit more far-ranging than the one of not having value_deleter, as you would have to forward (nearly) every member to a std::vector<char>, which would be vastly more laborious wrapping code, and implement your own NULL terminator handling, which is definitely bad news. In addition, everybody has to deal with literal strings, but only a few people have to deal with direct interoperation with such APIs- most simply write a wrapper over it and move on.

Of course, that approach would have solved the problem of it's ridiculously bloated interface. And I honestly don't believe that std::string does justify it's position in the Standard, given it's abysmal handling of encodings and whatnot. And I do intend to submit a proposal to deprecate it and replace it.

I'm not sure I'd be against something like unique_handle. But there's no need for a whole new class- the existing unique_ptr infrastructure can deal with it. And this is what I meant originally- that the correct resolution is to propose an extension to unique_ptr.

As for dealing with minor proposals, then I believe the new study group system will reduce the problem quite a bit. But I don't know by how much and I do think that it is worth considering. Ultimately, the only way to find out for sure would be to ask the Committee.

Andrew Sandoval

unread,
Nov 5, 2012, 5:16:25 PM11/5/12
to std-pr...@isocpp.org

I'm glad to see that the size produced by std::unique_ptr is small.  The problems I have are;

1) It's still called unique_ptr, when we aren't dealing with a pointer.

2) It requires a very limited lambda for something that should not -- not terrible, but still...

3) It not obvious.  Being one that avoids pointers where possible, I wouldn't be inclined to use it.  It just seems wrong to start passing around a pointer to an int, just to dereference it wherever it is used.

Seeing though is helpful.  Thanks.
-Andrew Sandoval

Andrew Sandoval

unread,
Nov 5, 2012, 5:33:43 PM11/5/12
to std-pr...@isocpp.org
On Sunday, November 4, 2012 4:44:53 PM UTC-6, DeadMG wrote:
I didn't mean to sound like a jerk, and I didn't mean to jeer at you. Silly Google Groups ate my original reply, so let me be brief.

Your need for very tight binary code is just that, your need. The Standard needs to consider everyone's needs. Your need is very niche, and very specialist. There's no reason the Committee would severely impair a general-purpose interface which works fine for virtually everybody for the vast minority. It's much better to ask you to write a new class for your specific need than to ask everybody to write a new class. They already considered the impact of unique_ptr's interface, and decided on it. It shouldn't yield any worse code size than your RAIIWrapper, and if it did, then call your compiler vendor- there's nothing the Committee can do about poor QoI.

As for non-pointer resources, I believe it should be perfectly possible to use unique_ptr with them. If not, consider proposing an extension to it that would change this. Introducing a second class for the same purpose- unique ownership of a resource- is not the solution. After all, what next, another new class for shared ownership?

As for RAIIFunction, you should at least start by converting it's interface to mirror that of the Standard, and propose an interface for it which matches that of the normal Standard components, like std::scope_exit or something.

Thanks, I can understand that.  I disagree with the need being mine alone, but I understand that you weren't curt on purpose and I appreciate your response.  It would also be very helpful if we had a way of knowing who is who on here.  You talk (in various posts) as if you've been involved in the committee and know what they would accept or reject.  If so, it certainly changes the weight of your feedback.

The problem that I have with RAIIFunction -- and the reason I've not converted it to mirror a standard interface is that I know it has a fundamental problem that others will reject it for -- and I don't know any good way to solve it.  Because std::tr1::function (and when used std::tr1::bind) can throw, the construction of RAIIFunction will not be trusted.  I don't know how to fix that unless there is a way to do it through a language modification.

The need is clearly there though.  The Boost library accepted BOOST_SCOPE_EXIT (Scope.Exit) which accomplishes the same thing via macros.  Macros may solve the exception case, but IMO at a terrible cost.  The last time I checked most debuggers are still mostly blind to the content of macros.

I'm certainly open to ideas.  I wanted to show what I'd done with RAIIFunction.  I can certainly rename it to scope_exit, or raii_function, but my hope was that everyone could look past the style long enough to see if there might be a way to make the concept work without hitting problems around function constructor exceptions.

-Andrew Sandoval

Nicol Bolas

unread,
Nov 5, 2012, 5:49:30 PM11/5/12
to std-pr...@isocpp.org

I've never really understood the point ofa generalized scope_exit syntax in C++. It makes more sense in a language like C, where RAII objects don't exist. I mean, if you want to create something and ensure it gets cleaned up, then write an object for it. It's that simple. That way, when you use it in multiple places, you won't have to copy all those silly "scope exist" bits around. You can also transfer the cleanup, thanks to move.

I wrote one for the xml_document type in LibXML2. It even automatically converts itself into an xml_document*, so you can use it anywhere you could before. It just cleans up after itself.

In virtually all of these "scope exit" cases, there is a specific resource that you want to key off of and clean up. That's what RAII is designed for, so just write a little RAII wrapper and go to it.

If you want to bring the scope stuff from D into C++, then at least bring in the useful part. Namely, being able to differentiate between scope exiting due to failure (ie: the stack unwinding from throwing an exception) and scope exiting due to success (ie: the stack ending naturally, without exception unwinding). Oh, and make it a real language feature, not some hack function bit that you have to use a non-capturing lambda to access.

DeadMG

unread,
Nov 5, 2012, 6:13:32 PM11/5/12
to std-pr...@isocpp.org
I'm not on the Committee and Bristol will be my first meeting. However, I have engaged with a fair few people who are on the Committee. When you ask questions like "How does std::forward work?", "Why wasn't proposal X accepted?" etc, then the guy who responds is on the Committee. http://stackoverflow.com/questions/6635656/the-structure-of-stdforward In addition, the people I know also regularly meet other people who are Committee members. But more generally, just look at their existing history.

I have no doubt that there is a fine possibility that the Committee would accept a scope_exit class, if one were to arrive well-written. Hell, I asked Herb Sutter if they would consider a new IO library, and he said yes. The trick is to create an interface that's clean and in line with the existing library so that it's easy to intergrate with idioms that are familiar.

Now, I'm fairly sure that you can get some nice nothrow guarantees. Consider something like this:

namespace std {
    template<typename T> class scope_exit_impl {
        T t;
    public:
        scope_exit_impl(T&& arg) : t(std::move(arg)) {}
        scope_exit_impl(const T& arg) : t(arg) {}
        ~scope_exit_impl() { t(); }
    };
    template<typename T> scope_exit_impl<T> scope_exit(T t) {
         return scope_exit_impl(std::move(t));
    }
}
int main() {
    auto&& exit_var = std::scope_exit([] { ... });
}

I'm pretty sure that in this case, scope_exit is guaranteed nothrow. But secondly, in a very real sense, the probability and problem of std::function throwing an exception is very limited. I wouldn't consider it a serious issue at all.
In fact, and this would amuse me greatly if it were true, I have a sneaking suspicion you could use, of all things, unique_ptr to do this. Surely an appropriate deleter... hmm.

Andrew Sandoval

unread,
Nov 5, 2012, 6:37:05 PM11/5/12
to std-pr...@isocpp.org
On Monday, November 5, 2012 4:49:31 PM UTC-6, Nicol Bolas wrote:
I've never really understood the point ofa generalized scope_exit syntax in C++. It makes more sense in a language like C, where RAII objects don't exist. I mean, if you want to create something and ensure it gets cleaned up, then write an object for it. It's that simple. That way, when you use it in multiple places, you won't have to copy all those silly "scope exist" bits around. You can also transfer the cleanup, thanks to move.

I wrote one for the xml_document type in LibXML2. It even automatically converts itself into an xml_document*, so you can use it anywhere you could before. It just cleans up after itself.

In virtually all of these "scope exit" cases, there is a specific resource that you want to key off of and clean up. That's what RAII is designed for, so just write a little RAII wrapper and go to it.

If you want to bring the scope stuff from D into C++, then at least bring in the useful part. Namely, being able to differentiate between scope exiting due to failure (ie: the stack unwinding from throwing an exception) and scope exiting due to success (ie: the stack ending naturally, without exception unwinding). Oh, and make it a real language feature, not some hack function bit that you have to use a non-capturing lambda to access.

Now that I couldn't disagree with more!  Not to sound unkind, but we don't want to throw out generic programming or functional programming, and I think that is what you are suggesting.

-Andrew Sandoval

Andrew Sandoval

unread,
Nov 5, 2012, 10:30:47 PM11/5/12
to std-pr...@isocpp.org

So, I really like that as a replacement for RAIIFunction.  It isn't a good replacement for RAIIWrapper because you can't access the resource, but to replace RAIIFunction it is great.

For anyone wanting to see it, the following code works.  It is almost identical to the code DeadMG posted -- I just had to make small fixes for my compilers (VS 2010 and 2012), and I added the cancel and invoke methods.  Since lambdas are the most common use, I didn't try to add a copy operator.  Thoughts?  Suggestions?

namespace std
{
   
template<typename T> class scope_exit_impl
   
{

        T m_tFunc
;
       
bool m_bExecute;
   
public:
        scope_exit_impl
(T&& arg) : m_tFunc(std::move(arg)), m_bExecute(true)
       
{
       
}

        scope_exit_impl
(const T& arg) : m_tFunc(arg), m_bExecute(true)
       
{
       
}

       
~scope_exit_impl()
       
{
           
if(m_bExecute)
           
{
                m_tFunc
();
           
}
       
}

       
//
       
// Cancel
        scope_exit_impl
& cancel()
       
{
            m_bExecute
= false;
           
return *this;
       
}

       
//
       
// Invoke...(e.g. early invocation)
        scope_exit_impl
& invoke(bool bCancelAfterInvoke = true)
       
{
            m_tFunc
();
            m_bExecute
= !bCancelAfterInvoke;
           
return *this;

       
}
   
};

   
template<typename T> scope_exit_impl<T> scope_exit(T t)
   
{

       
return scope_exit_impl<T>(std::move(t));
   
}
}

void Test()
{
   
auto&& exit_var = std::scope_exit([]()
   
{
        printf
("Exiting Test\n");
   
});
    printf
("In Test\n");
}

void Test2()
{
   
auto exit_var = std::scope_exit([]()
   
{
        printf
("Exiting Test 2\n");
   
});
    printf
("In Test 2\n");
}

void Test3()
{
   
auto &&fn = std::bind(&MessageBoxA, static_cast<HWND>(0), "Test 3", "Test 3", MB_TOPMOST);
   
auto &&exit_var = std::scope_exit(fn);
    printf
("In Test 3\n");
}

void Test4()
{
    HANDLE hEvent
= CreateEvent(nullptr, FALSE, FALSE, nullptr);
   
auto exit_var = std::scope_exit([&hEvent]()
   
{
       
if(nullptr != hEvent)
       
{
           
CloseHandle(hEvent);
       
}
   
});
    printf
("In Test 4\n");
}

void TestCancel()
{
   
auto exit_var = std::scope_exit([]()
   
{
        printf
("Exiting Cancel\n");
   
});
    printf
("In TestCancel\n");
    exit_var
.cancel();
}

void TestConst()
{
   
const auto &&exit_var = std::scope_exit([]()
   
{
        printf
("Exiting Const\n");
   
});
   
// exit_var.invoke();        // compiler error as expected
    printf
("In TestConst\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
   
Test();
   
Test2();
   
Test3();
   
Test4();
   
TestCancel();
   
TestConst();
   
return 0;
}



Can we agree that such a thing would be a good library to propose adding to the standard library?

Thanks Much,
Andrew Sandoval

Ville Voutilainen

unread,
Nov 6, 2012, 1:39:14 AM11/6/12
to std-pr...@isocpp.org
On 6 November 2012 05:30, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> So, I really like that as a replacement for RAIIFunction. It isn't a good
> replacement for RAIIWrapper because you can't access the resource, but to
> replace RAIIFunction it is great.

I wouldn't think it too complicated to add operator-> and operator* to the mix.

> For anyone wanting to see it, the following code works. It is almost
> identical to the code DeadMG posted -- I just had to make small fixes for my
> compilers (VS 2010 and 2012), and I added the cancel and invoke methods.
> Since lambdas are the most common use, I didn't try to add a copy operator.
> Thoughts? Suggestions?

I would use reset() like unique_ptr for the invoke+cancel, and release() for
cancel.

> Can we agree that such a thing would be a good library to propose adding to
> the standard library?

I think it would be a worthwhile addition.

Cassio Neri

unread,
Nov 6, 2012, 4:01:20 AM11/6/12
to std-pr...@isocpp.org

> Can we agree that such a thing would be a good library to propose adding to
> the standard library?

I think it would be a worthwhile addition.

If anyone is to write a proposal, I suggest to contact Andrei Alexandrescu because he and Petru Marginean wrote a similar feature, ScopeGuard, back in 2000 [1]. More recently Andrei did a revamp of ScopeGuard using C++11 features and presented it in the C++ and Beyond 2012. An implementation is provided in Folly [2]. See also the references in [2].

HTH,
Cassio.

[1] http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758?pgno=1
[2] https://github.com/facebook/folly/blob/master/folly/ScopeGuard.h

Nicol Bolas

unread,
Nov 6, 2012, 12:10:23 PM11/6/12
to std-pr...@isocpp.org

I don't understand this thinking. I don't see how this throws out "generic programing or functional programming." The whole point of a multi-paradigm language is that you get to use the paradigm that best serves your needs at the time. Nobody's stopping you from putting this in generic or functional code.

Exiting scope is best done as a RAII because virtually every cases of it is bound to some specific resources. Once it's in a RAII object, you can actually pass it around, transfer ownership, and all of those other things you can't do with a static scope exit.

Andrew Sandoval

unread,
Nov 6, 2012, 3:49:42 PM11/6/12
to std-pr...@isocpp.org
On Tuesday, November 6, 2012 3:01:20 AM UTC-6, Cassio Neri wrote:

> Can we agree that such a thing would be a good library to propose adding to
> the standard library?

I think it would be a worthwhile addition.

If anyone is to write a proposal, I suggest to contact Andrei Alexandrescu because he and Petru Marginean wrote a similar feature, ScopeGuard, back in 2000 [1]. More recently Andrei did a revamp of ScopeGuard using C++11 features and presented it in the C++ and Beyond 2012. An implementation is provided in Folly [2]. See also the references in [2].

HTH,
Cassio.


Sure, if you can get them on here I'd be glad of the support.  I'm not worried so much about who writes the proposal as much as that we get it done.  I'm new to the process but willing.
-Andrew Sandoval

Andrew Sandoval

unread,
Nov 6, 2012, 3:59:38 PM11/6/12
to std-pr...@isocpp.org
On Tuesday, November 6, 2012 11:10:23 AM UTC-6, Nicol Bolas wrote:

I don't understand this thinking. I don't see how this throws out "generic programing or functional programming." The whole point of a multi-paradigm language is that you get to use the paradigm that best serves your needs at the time. Nobody's stopping you from putting this in generic or functional code.

Exiting scope is best done as a RAII because virtually every cases of it is bound to some specific resources. Once it's in a RAII object, you can actually pass it around, transfer ownership, and all of those other things you can't do with a static scope exit.

I like the idea of having generic RAII wrappers.  I don't like the idea of writing new classes to encapsulate every type of resource.  The generic wrappers for me extend generic programming and functional programming.  They help produce cleaner code, more quickly.  Writing object wrappers around everything is tedious and produces a lot of code that will never get used, because when you work -- as I and my colleagues do, at a very low level, you need to see which system APIs are being used, and an object wrapper obscures that, where as the classes I've suggested here do not.  I don't think that is limited to guys that spend a lot of time at the level I'm speaking of (I do a lot of code injection, API hooking, etc.) -- I think it applies generally.  In the Windows world, WTL was a hit because MFC obscured and bloated too much.

There is definitely a place for object wrappers.  I'm sure the new thread classes, and file system objects will have huge benefit.  But, despite having had IOStreams for a very long time, there is still a very legitimate need to use open/close/read/readv/pread/write/writev/pwrite or their system-level equivalents on other platforms!

-Andrew Sandoval

I hope that makes sense.  I don't know a better way of explaining it.

Nicol Bolas

unread,
Nov 6, 2012, 4:33:26 PM11/6/12
to std-pr...@isocpp.org


On Tuesday, November 6, 2012 12:59:38 PM UTC-8, Andrew Sandoval wrote:
On Tuesday, November 6, 2012 11:10:23 AM UTC-6, Nicol Bolas wrote:

I don't understand this thinking. I don't see how this throws out "generic programing or functional programming." The whole point of a multi-paradigm language is that you get to use the paradigm that best serves your needs at the time. Nobody's stopping you from putting this in generic or functional code.

Exiting scope is best done as a RAII because virtually every cases of it is bound to some specific resources. Once it's in a RAII object, you can actually pass it around, transfer ownership, and all of those other things you can't do with a static scope exit.

I like the idea of having generic RAII wrappers.  I don't like the idea of writing new classes to encapsulate every type of resource.

I don't much care for the idea of writing this every time I want to use a particular resource:

FILE *pFile = fopen(...);
RAIIExitScope SomeNameIDontWantToConflictWithAnything([&] {fclose(pFile);})

Even the in-language version is not much better:

FILE *pFile = fopen(...);
scope
(exit) { fclose(pFile); }

I have to remember to do that every time I `fopen` a file. Why? There's no point.

This is much more compact, safer, and cannot be broken (not without perfidity):

MyFile file(...);

This always works. It will always clean up after itself. I don't clutter up local code with destruction code, thus decreasing the amount of code I need to look at to maintain this function. I will never forget to close the file. Even better:

MyFile SomeFunction(...)
{
 
MyFile file(...);
 
//Do stuff with file.
 
return file;
}

The resource is no longer bound to that scope. You can move the resource elsewhere without problems.

You can't do that with your method; once the scope ends, the file will be destroyed. Yes, you can restructure your code so that you create the file and pass it as an argument. But it may not be as natural as doing it this way.

This way works. All the time. You can use it in functional code. You can use it in generic code.

  The generic wrappers for me extend generic programming and functional programming.  They help produce cleaner code, more quickly.  Writing object wrappers around everything is tedious and produces a lot of code that will never get used, because when you work -- as I and my colleagues do, at a very low level, you need to see which system APIs are being used, and an object wrapper obscures that, where as the classes I've suggested here do not. 

"will never get used" is quite frankly an absurd statement. I'm sure that you and your colleagues may not use it, but that doesn't mean other people won't.
 
I don't think that is limited to guys that spend a lot of time at the level I'm speaking of (I do a lot of code injection, API hooking, etc.) -- I think it applies generally.  In the Windows world, WTL was a hit because MFC obscured and bloated too much.

To the extent that WTL is a big hit. On Stack Overflow for example, I see 125 WTL questions. I see over 4,000 MFC ones. That doesn't mean WTL is certainly less widely used then MFC, but my point is that your claim is fairly unsubstantiated.
 
There is definitely a place for object wrappers.  I'm sure the new thread classes, and file system objects will have huge benefit.  But, despite having had IOStreams for a very long time, there is still a very legitimate need to use open/close/read/readv/pread/write/writev/pwrite or their system-level equivalents on other platforms!

Note that the only reason to use them is because iostreams is a horribly conceived API that I hope someone writes a proposal that obsoletes the need of ever using it again. It's not bad because it wraps system calls; it's bad because it is bad.

std::vector wraps system calls too. So do all of the standard library stuff. You can't use most of the standard library when writing, for example, kernel-level code. That doesn't mean we need should have standards support for a non-throwing std::vector or something. Those programmers understand that they're working in the lowest levels, and that there are few guarantees there.

It seems to me that you've fallen into the "Objects are bad, never use them GRR!!!" mentality. We should not add standard library features or language support for people who are afraid of using basic language features like RAII properly.

Jeffrey Yasskin

unread,
Nov 6, 2012, 4:37:12 PM11/6/12
to std-pr...@isocpp.org
On Tue, Nov 6, 2012 at 1:33 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
>
> On Tuesday, November 6, 2012 12:59:38 PM UTC-8, Andrew Sandoval wrote:
>>
>> On Tuesday, November 6, 2012 11:10:23 AM UTC-6, Nicol Bolas wrote:
>>>
>>>
>>> I don't understand this thinking. I don't see how this throws out
>>> "generic programing or functional programming." The whole point of a
>>> multi-paradigm language is that you get to use the paradigm that best serves
>>> your needs at the time. Nobody's stopping you from putting this in generic
>>> or functional code.
>>>
>>> Exiting scope is best done as a RAII because virtually every cases of it
>>> is bound to some specific resources. Once it's in a RAII object, you can
>>> actually pass it around, transfer ownership, and all of those other things
>>> you can't do with a static scope exit.
>>
>>
>> I like the idea of having generic RAII wrappers. I don't like the idea of
>> writing new classes to encapsulate every type of resource.
>
>
> I don't much care for the idea of writing this every time I want to use a
> particular resource:
>
> FILE *pFile = fopen(...);
> RAIIExitScope SomeNameIDontWantToConflictWithAnything([&] {fclose(pFile);})
>
> Even the in-language version is not much better:
>
> FILE *pFile = fopen(...);
> scope(exit) { fclose(pFile); }
>
> I have to remember to do that every time I `fopen` a file. Why? There's no
> point.

So don't use the generic RAII wrapper for cleanup actions that happen
multiple times. However, just like we don't force people to extract
every multi-line block into a function, we shouldn't force people to
extract every cleanup action into a destructor. There's space for both
patterns in the language, and people should pick between them based on
how often the code is re-used.

Jeffrey

Andrew Sandoval

unread,
Nov 6, 2012, 4:38:20 PM11/6/12
to std-pr...@isocpp.org

Okay, I can change cancel to release, and invoke to reset() w/o the parameter.  What would you do with the operator-> and operator*?
Thanks,
Andrew Sandoval

Nicol Bolas

unread,
Nov 6, 2012, 4:45:32 PM11/6/12
to std-pr...@isocpp.org

But they won't. History has made it very clear that people will pick between them based on what is easiest at the time. And it is far easier to use a scope(exit) than it is to make a proper object, even if you stick that in 30 places in your source code.

We shouldn't add features that make writing bad code easier; C++ has plenty of that as is. RAII is the standard C++ method for solving resource cleanup issues; we shouldn't make it easy to avoid RAII in favor of something that looks like RAII but is easier to write in the short-term and is a huge burden to maintain in the long-term.

I guarantee you that the very minute scope(exit) or whatever hits the language/library and a functioning compiler implementation, you'll see far fewer RAII objects and a lot more mysterious resource failures.

Jeffrey Yasskin

unread,
Nov 6, 2012, 4:50:58 PM11/6/12
to std-pr...@isocpp.org
I've heard that style of argument before, and it doesn't convince me
in this case. Good luck with the other committee members.

Andrew Sandoval

unread,
Nov 6, 2012, 4:56:28 PM11/6/12
to std-pr...@isocpp.org

I'm not suggesting that at all.  Objects are fine, just not everything needs to be it's own object, with it's own destructor, etc.

And, last time I checked std::vector doesn't use system APIs, unless you take the default allocator, and that is only if you consider new a system-API.

And, I write kernel-level code, and I use my RAII objects there.  That was why I was looking for a no-throw equivalent to RAIIFunction.  My RAIIWrapper works great in Windows kernel drivers!  I've even worked on Windows kernel code where STL was used, because one of my associates found a very clever way of making certain constructors wouldn't throw but could still be checked!

You see, there is a place for all of these things.  Just because you find value in wrapping up every API in an object doesn't negate the need for specific RAII wrappers like scope_exit.

You have to understand, I'm not arguing for the sake of arguing -- I see your point, and there is some validity to it, but it isn't universal any more than what I'm proposing fits every need.

Thanks,
Andrew Sandoval

Daniel Krügler

unread,
Nov 6, 2012, 5:06:08 PM11/6/12
to std-pr...@isocpp.org
2012/11/6 Nicol Bolas <jmck...@gmail.com>

But they won't. History has made it very clear that people will pick between them based on what is easiest at the time. And it is far easier to use a scope(exit) than it is to make a proper object, even if you stick that in 30 places in your source code.

We shouldn't add features that make writing bad code easier; C++ has plenty of that as is. RAII is the standard C++ method for solving resource cleanup issues; we shouldn't make it easy to avoid RAII in favor of something that looks like RAII but is easier to write in the short-term and is a huge burden to maintain in the long-term.

I guarantee you that the very minute scope(exit) or whatever hits the language/library and a functioning compiler implementation, you'll see far fewer RAII objects and a lot more mysterious resource failures.

I think that very similar arguments could be applied to lambda expressions versus "hand-written"
function objects. While there surely will be examples in the wild where lambda expressions are
misapplied I have not found evidence yet that they significantly increase "mysterious object lifetime
errors" compared to other function objects.

- Daniel

Nevin Liber

unread,
Nov 6, 2012, 5:06:04 PM11/6/12
to std-pr...@isocpp.org
On 6 November 2012 15:45, Nicol Bolas <jmck...@gmail.com> wrote:
But they won't. History has made it very clear that people will pick between them based on what is easiest at the time. And it is far easier to use a scope(exit) than it is to make a proper object, even if you stick that in 30 places in your source code.

We shouldn't add features that make writing bad code easier; C++ has plenty of that as is. RAII is the standard C++ method for solving resource cleanup issues; we shouldn't make it easy to avoid RAII in favor of something that looks like RAII but is easier to write in the short-term and is a huge burden to maintain in the long-term.

Of course, the status quo is that the easiest code to write is calling the cleanup function at the end of the function instead of wrapping it in any kind of RAII wrapper at all, assume that all of the function exit code is written correctly, and hope that nothing throws an exception.
-- 
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Andrew Sandoval

unread,
Nov 6, 2012, 5:15:11 PM11/6/12
to std-pr...@isocpp.org

This is a very good point!  One of the reasons I push this so hard is because I've found that when people start using RAII wrappers like those I'm proposing, two things happen:
1) Their code becomes exception safe, and
2) They really start thinking about resource lifetime, and therefore code quality dramatically increases

What I'm tired of seeing is goto's used to deal with resource clean-up in C++ code, and deeply nested blocks that exist just to be sure that the clean-up at the bottom of the function runs -- when in fact it can't be guaranteed at all because it's not exception safe.

-Andrew Sandoval


Nicol Bolas

unread,
Nov 6, 2012, 6:01:40 PM11/6/12
to std-pr...@isocpp.org

This really sounds like the bare minimum solution. Yes, you get rid of gotos and such. Yes, if they follow the rules and use the scope(exit) code everywhere, they will get exception-safe code.

But that doesn't change the fact that the code is there, rather than associated with the resource. That doesn't change the fact that the destruction code has to be copy-and-pasted everywhere, and that people will do so. That doesn't change the fact that you can't remove the resource from that scope and transfer ownership of it.

The functionality of the code will be improved, in that you're more likely to get resources released and better exception behavior. But it's going to be that much harder to eventually get them to use actual RAII, because they'll just say, "Oh, I'll just use a scope(exit); no point in creating an object. Everyone knows those are all slow and bloated, after all."

It reinforces the already existing (and incorrect) stigma that object == bad, repeated local code == good.

Andrew Sandoval

unread,
Nov 6, 2012, 6:15:38 PM11/6/12
to std-pr...@isocpp.org

I don't think so.  Because there is a big advantage to putting things like an RAIIWrapper<RAIIDeleter(close), -1> m_iFileDescriptor; right into your File object, so that you don't need an explicit destructor.

Does your distaste for this take you to the point where you'd not use std::unique_ptr?  Because you can simply call delete in your object's destructor?

-Andrew Sandoval

 

Nevin Liber

unread,
Nov 7, 2012, 11:05:50 AM11/7/12
to std-pr...@isocpp.org
On 6 November 2012 17:15, Andrew Sandoval <sand...@netwaysglobal.com> wrote:

I don't think so.  Because there is a big advantage to putting things like an RAIIWrapper<RAIIDeleter(close), -1> m_iFileDescriptor; right into your File object, so that you don't need an explicit destructor.

And one of the advantages to this is that it appears visually close to use, where type definitions do not.  Sidebar: this is one of the advantages of lambdas over function objects; in practical terms, I've gotten many more people to embrace algorithms because of this advantage.

Nevin Liber

unread,
Nov 7, 2012, 11:25:07 AM11/7/12
to std-pr...@isocpp.org
On 6 November 2012 17:01, Nicol Bolas <jmck...@gmail.com> wrote:

The functionality of the code will be improved, in that you're more likely to get resources released and better exception behavior. But it's going to be that much harder to eventually get them to use actual RAII, because they'll just say, "Oh, I'll just use a scope(exit); no point in creating an object. Everyone knows those are all slow and bloated, after all."

I rarely try to argue against emotional pleas; that is a losing battle.  My usual counter is "show me your measurements".  Also, who is to say that for a one-liner, using an RAII Wrapper isn't the right answer?

It reinforces the already existing (and incorrect) stigma that object == bad, repeated local code == good.

Local code has advantages; we should be finding ways to embrace those advantages.  Lambdas, for instance, are a good first step.
 
Do you believe the status quo is good?  Because if we do nothing, that is where we will stay.

Writing objects in C++ is incredibly tedious.  I'd like to reduce that tedium; otherwise, for "small" things, people just fall back on abusing native types and writing C style code.

Martinho Fernandes

unread,
Nov 7, 2012, 11:41:19 AM11/7/12
to std-pr...@isocpp.org
On Wed, Nov 7, 2012 at 5:25 PM, Nevin Liber <ne...@eviloverlord.com> wrote:
It reinforces the already existing (and incorrect) stigma that object == bad, repeated local code == good.

Local code has advantages; we should be finding ways to embrace those advantages.  Lambdas, for instance, are a good first step.

The word "repeated" was found somewhere in the vicinity of "local code" in the original text.

Nevin Liber

unread,
Nov 7, 2012, 12:27:35 PM11/7/12
to std-pr...@isocpp.org

That is the tension, isn't it?  Still, small lambdas (and before that, bind expressions) get repeated all the time.  While I'm certainly a fan of DRY (Don't Repeat Yourself), I'm not convinced that never repeating yourself is the right answer.

Nicol Bolas

unread,
Nov 7, 2012, 1:38:57 PM11/7/12
to std-pr...@isocpp.org


On Wednesday, November 7, 2012 8:25:53 AM UTC-8, Nevin ":-)" Liber wrote:
Do you believe the status quo is good?  Because if we do nothing, that is where we will stay.

The status quo really depends on the programmers around you. If they're properly trained in the ways of RAII, then there's no problem with the status quo. They'll create RAII types that are moveable and everyone will be happy.

If they're bad, C-in-C++ style programmers, then the status quo is that they'll continue to write C-in-C++ style programs. You can either break them of this habit, or add language features and library routines that indulge their hang-ups and idioms.

Personally, I prefer the former. A clean break with their bad style of coding makes them more likely to pick up good idioms. Indulging their predilections reinforces the idea that they're right to do C-in-C++ coding.
 
Writing objects in C++ is incredibly tedious. I'd like to reduce that tedium; otherwise, for "small" things, people just fall back on abusing native types and writing C style code.

Which people? Your coworkers, perhaps. Other people, perhaps. But there are many C++ programmers who embraced RAII long ago, regardless of how "incredibly tedious" it was. C-in-C++ style is not the only style of C++ you'll find; there are many C++-style programmers running around.

I personally would find it far more tedious to write little lambdas and such in every place I use a resource just to prevent resource leaks, when writing a single object once would solve the problem everywhere.
 

Nevin Liber

unread,
Nov 7, 2012, 2:12:29 PM11/7/12
to std-pr...@isocpp.org
On 7 November 2012 12:38, Nicol Bolas <jmck...@gmail.com> wrote:

Which people? Your coworkers, perhaps. Other people, perhaps. But there are many C++ programmers who embraced RAII long ago, regardless of how "incredibly tedious" it was.

And removing the tedium of writing small classes will help us too.

I personally would find it far more tedious to write little lambdas and such in every place I use a resource just to prevent resource leaks, when writing a single object once would solve the problem everywhere.

For instance, do you really find:

auto i = std::find_if(c.begin(), c.end(), [&](decltype(*c.end()) _){ return _.name == name; });

worse than

auto i = std::find_if(c.begin(), c.end(), MatchNameInFoo(name));

I need no other context to understand the first line, and it is simple enough that a bug is unlikely to creep in (and will get even simpler with polymorphic lambdas).  Things like lambdas and bind exist so that people don't have to write those small classes by hand.  Most people I know consider that a good thing.

I'd like the same thing for simple resource cleanup.  I already have that when the resource is accessed via a pointer (unique_ptr); why not generalize it?

The problem isn't writing one single object; it's having to write, name and organize dozens to hundreds of different objects that do slightly different things.


Of course, if you can point me to dozens of code bases where people have embraced explicit RAII throughout their code, I'm certainly willing to change my opinion.

Andrew Sandoval

unread,
Nov 7, 2012, 2:51:22 PM11/7/12
to std-pr...@isocpp.org
On Wednesday, November 7, 2012 1:13:11 PM UTC-6, Nevin ":-)" Liber wrote:
On 7 November 2012 12:38, Nicol Bolas <jmck...@gmail.com> wrote:

Which people? Your coworkers, perhaps. Other people, perhaps. But there are many C++ programmers who embraced RAII long ago, regardless of how "incredibly tedious" it was.

And removing the tedium of writing small classes will help us too.

I personally would find it far more tedious to write little lambdas and such in every place I use a resource just to prevent resource leaks, when writing a single object once would solve the problem everywhere.

For instance, do you really find:

auto i = std::find_if(c.begin(), c.end(), [&](decltype(*c.end()) _){ return _.name == name; });

worse than

auto i = std::find_if(c.begin(), c.end(), MatchNameInFoo(name));

I need no other context to understand the first line, and it is simple enough that a bug is unlikely to creep in (and will get even simpler with polymorphic lambdas).  Things like lambdas and bind exist so that people don't have to write those small classes by hand.  Most people I know consider that a good thing.

I'd like the same thing for simple resource cleanup.  I already have that when the resource is accessed via a pointer (unique_ptr); why not generalize it?

The problem isn't writing one single object; it's having to write, name and organize dozens to hundreds of different objects that do slightly different things.

Well said!
 

Of course, if you can point me to dozens of code bases where people have embraced explicit RAII throughout their code, I'm certainly willing to change my opinion.

That would not change my mind.  There is a place for this every bit as much as there is for unique_ptr!  Even for those that have "embraced explicit RAII throughout their code", there is a place for this.  You (Nicol) favor encapsulation.  I favor transparency.  And still in both cases this is useful as I've pointed out several times.  It is useful in ensuring loops advance on continue (scope-exit).  It's useful in a myriad of other situations.  And it is useful in writing those 'explicit RAII' objects.

Anecdotal as it may be, my experience is that the vast majority of C++ code compiled into shipping products today is:
1) "C-in-C++"
2) Not exception safe
3) Laden with resource leaks
4) Generally suffers from quality problems

I'd go so far as to say that the only reason that Java and .Net exist was to try to fix this, in a completely backwards and wrong way, not matter how good intentioned.  The result is that those languages now are suffering the same types of quality problems if not worse because the real problems were not addressed (and because the runtimes are written in sometimes poorly crafted C or C++).

Having and using RAII wrappers forces the programmer to consider resource lifetime on a regular basis.  It is transparent -- that is, the destruction of the resource is near it's allocation and use.  It is immensely helpful during code reviews!  None of this necessarily true with your full-blown objects, and because of that it doesn't accomplish the same paradigm shift that leads to higher quality code.

And, not to be rude, but it sounds like you are arguing against many of the new language features, like lambdas.  If so, I'm not sure what the point of continuing to argue this aspect of the issue.  I don't think we can convince you (Nicol) in particular that this or anything other than OO features are worthy of the standard.  If I'm wrong, and we can are advancing I'm glad for it, but if not I'd like some discussion on how we move forward, because I believe that there is sufficient agreement that scope_exit in particular, and maybe something similar to RAIIWrapper is worth adding to the Standard Library, even if it does not appeal to everyone.

Thanks,
Andrew Sandoval

Ville Voutilainen

unread,
Nov 7, 2012, 2:57:04 PM11/7/12
to std-pr...@isocpp.org
On 7 November 2012 21:51, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> worthy of the standard. If I'm wrong, and we can are advancing I'm glad for
> it, but if not I'd like some discussion on how we move forward, because I

Read the instructions in
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3370.html
and submit a proposal.

Herb Sutter

unread,
Nov 7, 2012, 3:01:36 PM11/7/12
to std-pr...@isocpp.org
> worthy of the standard. If I'm wrong, and we can are advancing I'm
> glad for it, but if not I'd like some discussion on how we move
> forward, because I

See also http://isocpp.org/std/submit-a-proposal for details.


Andrew Sandoval

unread,
Nov 8, 2012, 12:19:39 AM11/8/12
to std-pr...@isocpp.org, hsu...@microsoft.com
Thank you Herb and Ville, and everyone else that has helped get us to this point.  I'll work up a proposal as soon as I get some free time.  If anyone that has helped would like to be involved in the process, drop me an e-mail.

Thanks,
Andrew Sandoval

Arthur Tchaikovsky

unread,
Nov 13, 2012, 2:28:24 AM11/13/12
to std-pr...@isocpp.org
I personally would find it far more tedious to write little lambdas and such in every place I use a resource just to prevent resource leaks, when writing a single object once would solve the problem everywhere.

I couldn't agree more with that. I personally believe that lambdas are worse for this kind of programming, i.e. they don't make code more readable. High abstraction is better than low abstraction, so how is this:

RAIIExitScope SomeNameIDontWantToConflictWithAnything([&] {fclose(pFile);}) //Note the lambda

more readable/maintanable to this:

RAIIExitScope SomeNameIDontWantToConflictWithAnything(safe_delete);//functor instead of lambda

How can someone think that exposing guts (as lambdas do) is better than hiding them behind an abstraction level (as functors do)? This is simply not so.

Andrew Sandoval

unread,
Nov 13, 2012, 2:34:42 AM11/13/12
to std-pr...@isocpp.org
On Tuesday, November 13, 2012 1:28:25 AM UTC-6, Arthur Tchaikovsky wrote:
I personally would find it far more tedious to write little lambdas and such in every place I use a resource just to prevent resource leaks, when writing a single object once would solve the problem everywhere.

I couldn't agree more with that. I personally believe that lambdas are worse for this kind of programming, i.e. they don't make code more readable. High abstraction is better than low abstraction, so how is this:

RAIIExitScope SomeNameIDontWantToConflictWithAnything([&] {fclose(pFile);}) //Note the lambda

more readable/maintanable to this:

RAIIExitScope SomeNameIDontWantToConflictWithAnything(safe_delete);//functor instead of lambda

How can someone think that exposing guts (as lambdas do) is better than hiding them behind an abstraction level (as functors do)? This is simply not so.


In general I agree.  There are times however where a lambda is a perfect fit.  Very shortly I will post a (very rough) draft of the proposal and a reference implementation.  Please look through that and see the possibilities that the library provides.  The test cases that I've written are not complete, and not meant to be real-world examples -- some are quite contrived, but they still give a general idea on how the library can be used.

Thanks,
Andrew Sandoval

Andrew Sandoval

unread,
Nov 13, 2012, 2:48:49 AM11/13/12
to std-pr...@isocpp.org
For those willing to take a look at the proposal as a very rough draft, it can be found here: http://www.andrewlsandoval.com/scope_exit/

The Technical Specification section is completely blank.  Judging by the examples (and by my understanding of them) this portion will probably take me several days.  Of course, I'm grateful for any help, from anyone else that would like to see this functionality added to the Standard Library.  And similarly for any feedback on the document as it currently stands.  This is my first shot at this kind of thing!  So be gentle please! ;)

I'm also open to suggestions on the reference implementation.  I'm still adapting to the style difference, and like many, I'm not always certain yet that I get all of my universal and rvalue references right.  (Possibly other things as well.)

Thanks in advance,
Andrew Sandoval

Martinho Fernandes

unread,
Nov 13, 2012, 3:38:45 AM11/13/12
to std-pr...@isocpp.org
I don't think contrived non-real-world examples are convincing. Especially the second example, the one of "loop advancement": isn't that what a for loop is for? (and then I can't stop thinking about iterators and the algorithms)

Martinho


--
 
 
 

Ville Voutilainen

unread,
Nov 13, 2012, 4:20:44 AM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 09:48, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> For those willing to take a look at the proposal as a very rough draft, it
> can be found here: http://www.andrewlsandoval.com/scope_exit/
> The Technical Specification section is completely blank. Judging by the
> examples (and by my understanding of them) this portion will probably take
> me several days. Of course, I'm grateful for any help, from anyone else
> that would like to see this functionality added to the Standard Library.
> And similarly for any feedback on the document as it currently stands. This
> is my first shot at this kind of thing! So be gentle please! ;)
> I'm also open to suggestions on the reference implementation. I'm still
> adapting to the style difference, and like many, I'm not always certain yet
> that I get all of my universal and rvalue references right. (Possibly other
> things as well.)

Some thoughts:

About the motivation and unique_ptr, I don't have to dereference a value from
.get(), I can just use operator* when I need a reference to the
underlying value.
If I need the pointer, I need .get(), sure. It's understandable that
these wrappers
have a cast operator, since they aren't similar to smart pointers in
that sense and
there's no danger in getting the underlying object.

Should scoped_resource throw from its constructor if it gets a no-delete value?

Should we have operator-> for accessing the underlying value?

The _uc and _t suffixes are unfortunately unclear. I'm not going to
start a bikeshedding
right now, but I think the names need to be reconsidered.

Should these handles have make_ function counterparts? That would probably
alleviate the need for the SCOPED_DELETER macro.

Andrew Sandoval

unread,
Nov 13, 2012, 11:09:15 AM11/13/12
to std-pr...@isocpp.org
On Tuesday, November 13, 2012 2:39:06 AM UTC-6, R. Martinho Fernandes wrote:
I don't think contrived non-real-world examples are convincing. Especially the second example, the one of "loop advancement": isn't that what a for loop is for? (and then I can't stop thinking about iterators and the algorithms)

Martinho


I am open to suggestions.  I would be surprised to find out that other long time C++ programmers haven't seen similar problems with loop advancement -- because someone comes in a year after a product is shipping and while attempting to fix a bug adds a conditional continue to a loop which results in skipping over the loop advancement, producing a tight loop when that condition is hit.  Sure, it can be avoided by code reviews and greater care, but by using RAII you have the ability to code a bit more defensively.

I can't speak for everyone else out there -- my experience comes from real world commercial programming.  And I have to tell you that I would love it if everyone used algorithms like std::for_each instead old C style loops, but they don't and in many cases there is good justification for it.  C++ is strengthened if we add library components that help programmers to write code that begins with the end in mind.  That is what an RAII object for loop advancement does.

Thanks,
Andrew Sandoval

Martinho Fernandes

unread,
Nov 13, 2012, 11:23:21 AM11/13/12
to std-pr...@isocpp.org
On Tue, Nov 13, 2012 at 5:09 PM, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
I am open to suggestions.

I don't have any examples, because I am with Nicol on this: I don't think this is necessary. I wrote a similar scope guard thing before, and sometime later I realised that it was only used it in its own tests.
 
I would be surprised to find out that other long time C++ programmers haven't seen similar problems with loop advancement -- because someone comes in a year after a product is shipping and while attempting to fix a bug adds a conditional continue to a loop which results in skipping over the loop advancement, producing a tight loop when that condition is hit.  Sure, it can be avoided by code reviews and greater care, but by using RAII you have the ability to code a bit more defensively.

But there's a much simpler fix than adding whatever RAII object without an actual resource whose sole purpose is to run something at the end of each iteration: a for loop. Either that, or honestly I don't understand what these problems with loop advancement are.

My overall point is that if you really want this to fly, you need to provide convincing examples. Preferably examples that relate to real-world code somehow. You can't expect people to relate to this feature if there are no convincing use cases. You need to provide examples where this feature is superior to the alternatives, or where there are no reasonable alternatives at all. Those examples need to be code that someone would actually see themselves needing to write.

Martinho.

Andrew Sandoval

unread,
Nov 13, 2012, 11:37:24 AM11/13/12
to std-pr...@isocpp.org
Answers are inline below.  Thanks Ville!


On Tuesday, November 13, 2012 3:20:46 AM UTC-6, Ville Voutilainen wrote:

Some thoughts:

About the motivation and unique_ptr, I don't have to dereference a value from
.get(), I can just use operator* when I need a reference to the
underlying value.
If I need the pointer, I need .get(), sure. It's understandable that
these wrappers
have a cast operator, since they aren't similar to smart pointers in
that sense and
there's no danger in getting the underlying object.


Good point.  I've modified that paragraph.  Hopefully it now makes it clear why std::unique_ptr isn't the best fit.

Should scoped_resource throw from its constructor if it gets a no-delete value?

No!  The goal here is to replace code that does something like the following at the bottom of every function (which often lead to goto being used to exit on error):

ErrorExit:
    if(resource != no_delete_value)
    {
        delete_resource(resource)
    }


There may be arguments to be made for having a version of the classes that does throw, but having to catch exceptions would alter the scope and therefore the expected lifetime of the resource!  IMO, using the .valid() member is far superior than throwing.

In my experience, every time an exception handler must be used, nesting and complexity increase, and often unnecessarily.

Should we have operator-> for accessing the underlying value?

I'm not opposed to that.  I can add that if there is consensus.  With the get() method, and a cast operator it doesn't feel intuitive to use operator-> to me, but I could be convinced.

The _uc and _t suffixes are unfortunately unclear. I'm not going to
start a bikeshedding
right now, but I think the names need to be reconsidered.

I struggled with the names, and I am definitely open to suggestions.  I'm not even certain that scoped_resource_uc should be kept.  The uc was for unchecked.  Given that there is a function template to generate it I didn't think the type name was too important.  With the exception of scoped_resource_t I don't see the typenames being used very often.  Either way, I am not in any way married to the names.  If others can make suggestions on this, consistent with the point to the class, I will gladly work with them and create better names.

Remember that the most common expected use of scoped_function, scoped_resource_uc, and scoped_resource is when it is built using scope_exit() with parameters that create the appropriate type.  So, the left-hand side will usually be auto.

Because of the template arguments that is not true for scoped_resource_t.  I expect scoped_resource_t will also be the only one to be commonly found in member variables.

 
Should these handles have make_ function counterparts? That would probably
alleviate the need for the SCOPED_DELETER macro.

The already do in the form of the scope_exit.  I'd be happy to rename those to make_scoped_function / make_scoped_resource, etc.  Only scoped_resource_t does not, and that is because I can't see anyway to do it.  It requires two non-type template arguments.  Also, I want it to be usable in class members, and for that to work, the template arguments need to be present rather than deduced by template function.

If the SCOPED_DELETER macro is going to be a hang up, I'll remove all reference to it.  I would rather write every example using <decltype(&deleter_func), deleter_func> than to have the macro detract from the importance of scoped_resource_t.

Thanks again,
Andrew Sandoval



Ville Voutilainen

unread,
Nov 13, 2012, 12:22:32 PM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 18:37, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> Answers are inline below. Thanks Ville!

As they should be, top-posting is brain-damaged. :)

>> Should scoped_resource throw from its constructor if it gets a no-delete
>> value?
> No! The goal here is to replace code that does something like the following
> at the bottom of every function (which often lead to goto being used to exit
> on error):
> ErrorExit:
> if(resource != no_delete_value)
> {
> delete_resource(resource)
> }
> There may be arguments to be made for having a version of the classes that
> does throw, but having to catch exceptions would alter the scope and
> therefore the expected lifetime of the resource! IMO, using the .valid()
> member is far superior than throwing.

Lifetime of the resource? What resource? We just failed to acquire a resource.
Consider vector.

vector<int> f();

vector<int> x(f());

if the Acquisition of the Resource fails, Initialization fails as
well. Using open(),
if open() fails, we have no resource to operate on. Why are we
continuing forward
with the initialization, or the code beyond it? The response to an
initialization failure
in C++ is a throw. Now, for the handles that can't detect the failure,
we can't do that,
but for the handle that takes the "invalid" value as an argument, we
can, and likely
should.

> In my experience, every time an exception handler must be used, nesting and
> complexity increase, and often unnecessarily.

I have the completely opposite experience. I don't have to do nested checks
with exceptions, and the complexity of code is less than without exceptions.
I can decide where I put the complexity, and the no-error paths are fast and
clean because there's no checking of return values all over the place. That's
the reason people use RAII instead of return values or valid() checks.

>> Should we have operator-> for accessing the underlying value?
> I'm not opposed to that. I can add that if there is consensus. With the
> get() method, and a cast operator it doesn't feel intuitive to use
> operator-> to me, but I could be convinced.

When I use a handle, I want to use it for all accesses to the
underlying resource,
so if I have to call a member function of the underlying resource, I expect to
be able to do handle->func(), not handle.get().func() or, heaven forbid,
Resource& res = handle; res.func(); or Resource& res = handle.get(); res.func();
Handles are, as often as possible, as transparent as possible. That
would indicate
that we want to have, for the purposes of type-deducing function templates,
operator*(), and for things that want a pointer, operator&(), too.

Andrew Sandoval

unread,
Nov 13, 2012, 12:26:18 PM11/13/12
to std-pr...@isocpp.org
Martinho, I'm fairly certain no matter what examples I use I'm not going to convince everyone.  Somehow I'm fairly certain that my saying that I've been using classes like these for the last 6 or 7 years, and teaching others to use them instead of the completely unsafe methods that they default to, and that it has dramatically improved the quality of several products isn't going to convince you.  I simple don't have the right or the time to go and pull examples from these products and show the before and after.  It should be fairly obvious from the examples I've given, for any experienced C++ programmer.  If you and Nicol are on the committee and can tell me that this isn't going to make it through, then please do so, and I'll will place my efforts elsewhere.  Otherwise, please use your imagination and I am convinced you will see that this has every bit as much value as does std::unique_ptr, std::shared_ptr, and many other classes which are used to similar ends.  It is a very pragmatic solution to a real world problem.

My goal is to simplify what Nicol sees as the answer, and to simplify the alternative at the same time.  It is to improve overall code quality.  Everywhere I've worked I've found that most resources will be leaked on unwind, and are often leaked simply because the author didn't think about all of the exit paths that could lead to a leak.  (I've also seen some really nasty nesting to try to cover all exit paths and conditions.)  These types of things costs companies millions of dollars every day in lost productivity.  They are difficult problems to debug in complex code.  Most programmers don't have the skills necessary to discover that an exception handler further down the stack unwound their code and leaked a resource, like a lock for example, and then moved on and later hung or crashed, etc..  By the time they get a crash dump from the field it takes a lot of work to figure out what went wrong -- and most of them aren't even aware of the potential for their resource to leak on unwind.  And it's not like I am an amateur that has only worked for one company with some not so great of engineers.  I've been writing C++ professionally since 1993, and tackling problems most engineers consider too hard.  I've seen this a lot.  And no matter how hard you try you are not going to get everyone to write non-generic objects to wrap around every type of resource.  After all, I've recently worked at a place where some teams insisted on using straight-C for various idiotic reasons.  In one case my presentation on RAII, and an earlier version of scoped_resource_t (named very differently) was the closest thing there was to a convincing reason for that team to switch to C++.

It is easy to say "this is not needed at all", but remember that there are still people out there that think C++ isn't needed because they can always write their code in assembly language.  Guess what, I can too, but I don't want to.  I want to see the language improved to the point where it can win back the dominant position and make languages like Java and C# return to their status as being just a step above shell scripts and .bat files.  Where it is clear that you can get better quality, faster code from C++, and you don't have to put a monumental effort into it to produce code that does as much as few lines in one of those languages.

My understanding is that the point to this list, and to the proposals being generated is to promote C++ and good programming practices.  I believe that this proposal goes along way toward that effort.  Nevertheless, I'd much prefer to not waste time trying to convince those who for whatever reason aren't going to bend.

I'm very appreciative of the feedback from many others that have helped move this along.  I will try to find ways to make the examples more convincing, and I appreciate your suggestion.  Please however, if you are on the committee and intend to shoot this down no matter what, please make that clear.  This is a very time consuming process, and I don't have any to waste.

Thank you much,
Andrew Sandoval

Ville Voutilainen

unread,
Nov 13, 2012, 12:39:31 PM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 19:26, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> and I appreciate your suggestion. Please however, if you are on the
> committee and intend to shoot this down no matter what, please make that
> clear. This is a very time consuming process, and I don't have any to
> waste.

I am in the committee, and I have no intention to shoot this down. I
use RAII in loop bodies,
and it's useful there. I don't use for-loops as a RAII replacement,
namely because it DOES
NOT WORK. :) If I throw an exception from the loop body, the for loop
will not save me,
but a RAII handle will. And if I'm using a RAII handle to cope with
exceptions, I am going
to use the same handle to cope with goto/break/continue/return, too.

Nicol Bolas

unread,
Nov 13, 2012, 12:47:07 PM11/13/12
to std-pr...@isocpp.org

I'm pretty sure he's talking about using RAII as the thing that increments the loop counter. Like this:

int *currValue = &someArray[0];
for(int i = 0; i < size; ++i)
{
  scope
(exit) {++currValue;} //Every iteration will update the current value.
 
//...
 
if(soemthing)
 
{
   
continue;
 
}

 
//++currValue; //old way: skipped by the continue. By accident.
}



But obviously for more complex cases.

Ville Voutilainen

unread,
Nov 13, 2012, 12:49:31 PM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 19:47, Nicol Bolas <jmck...@gmail.com> wrote:
>> and it's useful there. I don't use for-loops as a RAII replacement,
>> namely because it DOES
>> NOT WORK. :) If I throw an exception from the loop body, the for loop
> I'm pretty sure he's talking about using RAII as the thing that increments
> the loop counter. Like this:

Yes, and sometimes the thing that increments the counter may throw.

Nicol Bolas

unread,
Nov 13, 2012, 12:57:38 PM11/13/12
to std-pr...@isocpp.org

I'm not sure I understand your point here. So what if it does?

Now, you have a real point in the general case that more complex cleanup has to be carefully written in the case of error. But this is not cleanup. This is really more about abusing cleanup to increment a counter. I don't know how that can cause a problem.

Vicente J. Botet Escriba

unread,
Nov 13, 2012, 1:02:06 PM11/13/12
to std-pr...@isocpp.org
Le 13/11/12 18:47, Nicol Bolas a écrit :
Hi,

The C solution to this problem is to put the scoped exit action on the for, isn't it?

int *currValue = &someArray[0];
for(int i = 0; i < size; ++i,++currValue)
{
  //...
 
if(soemthing)
 
{
   
continue;
 
}
}


Best,
Vicente


Ville Voutilainen

unread,
Nov 13, 2012, 1:02:15 PM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 19:57, Nicol Bolas <jmck...@gmail.com> wrote:
>> Yes, and sometimes the thing that increments the counter may throw.
> I'm not sure I understand your point here. So what if it does?
> Now, you have a real point in the general case that more complex cleanup has
> to be carefully written in the case of error. But this is not cleanup. This
> is really more about abusing cleanup to increment a counter. I don't know
> how that can cause a problem.

I may want to use the counter's value after the loop, and I may want
it to advance
no matter what happens in the loop. Ok, this is a bit theoretical, and
I think the loop
example, if used, needs to be bit more motivating. ;)

Nicol Bolas

unread,
Nov 13, 2012, 1:11:12 PM11/13/12
to std-pr...@isocpp.org

As I said, it would be used for more complex incrementing than something that could fit in a for-loop expression (or for non-for loops).

Vicente J. Botet Escriba

unread,
Nov 13, 2012, 2:30:49 PM11/13/12
to std-pr...@isocpp.org
Le 13/11/12 08:48, Andrew Sandoval a écrit :
For those willing to take a look at the proposal as a very rough draft, it can be found here: http://www.andrewlsandoval.com/scope_exit/

The Technical Specification section is completely blank.  Judging by the examples (and by my understanding of them) this portion will probably take me several days.  Of course, I'm grateful for any help, from anyone else that would like to see this functionality added to the Standard Library.  And similarly for any feedback on the document as it currently stands.  This is my first shot at this kind of thing!  So be gentle please! ;)

Hi,

I don't think you need to add on your proposal the types returned by the scoped_exit function. Just propose a function that returns an implementation defined type that provides a specific behavior.

I'm for the first overloading, which corresponds to the interface Boost.ScopedExit has to make code portable.

The example motivating the second overload not convince me as we can use the simpler scoped_exit overload

Instead of
void drive_any_available_bus()
{
    auto &&bus = std::scope_exit(lock_available_bus(), unlock_bus);     // Always unlock the bus when done driving...  No need to verify the resource first
    if(bus == -1)
    {
        return;     // No buses...
    }
    drive(bus);
}
the following should do the same
void drive_any_available_bus()
{
    if(lock_available_bus() == -1) return;
    auto &&bus = std::scope_exit(unlock_bus);
drive(bus); }
The same applies to the third overload

// Open a file, ensure it is closed when we leave scope, if the file descriptor is valid...
auto&& iFileDesc = std::scope_exit(_open(pszPath, O_WRONLY), _close, -1);		// if _open returns -1, don't call _close...
if(iFileDesc.valid())
{
    _write(iFileDesc, &data, sizeof(data));
}

could be replaced by


    
auto fd = _open(pszPath, O_WRONLY);
if (fd != -1) return; // ...
auto&& guard = std::scope_exit(  [&fd] { _close(fd); } );
_write(iFileDesc, &data, sizeof(data));
The last example concerns a resource wrapper with implicit conversion to the underlying resource. One of the problems with implicit conversions is that some overloads can be missed.
When wrapping a resource an alternative approach is to provide the specific functions, in your case define a File wrapper, but there is no need for a generic scoped_resource to implement it.

In particular the example could be implemented as
class TestClassMember3
{
private:
    FILE m_hEvent;
public:
    TestClassMember2() : m_hEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr))
    {
    }

    ~TestClassMember() 
    {
        CloseHandle(m_hEvent);
    } 
    // Other stuff here...
};
Resuming the simple scoped_exit is enough to make a useful C++14 proposal that covers all the examples you have on your current proposal and could have good chances to be accepted.
Of course this doesn't covers all the classes you wanted to propose, but ...

The proposal could be complemented with scoped_success, scoped_failure (based on D language). This could be possible updating std::uncaught_exception or any other mechanism so that failure or success conditions can be determined.

Just my 2 euro cents,
Vicente






Nicol Bolas

unread,
Nov 13, 2012, 2:59:31 PM11/13/12
to std-pr...@isocpp.org

That's the thing: if it's going to happen, I would much rather it be a language feature. Just compare the code:

auto &&log_status_on_exit = std::scope_exit([&status, &modifier]() ->void
{
 
LogStatus(status, "SomeFunction", modifier);
});

vs:

scope(exit)
{
 
LogStatus(status, "SomeFunction", modifier);
}

Isn't that so much better? No messy variables to create. No name collisions. No lambdas, and therefore no need to explicitly include anything in scope. It's immediately clear what it does.

That way, you don't need to modify std::uncaught_exception or anything like that. The system will call the `scope(failure)` at the correct time.

Vicente J. Botet Escriba

unread,
Nov 13, 2012, 4:22:06 PM11/13/12
to std-pr...@isocpp.org
Le 13/11/12 20:59, Nicol Bolas a écrit :


On Tuesday, November 13, 2012 11:30:53 AM UTC-8, viboes wrote:
Le 13/11/12 08:48, Andrew Sandoval a écrit :


The proposal could be complemented with scoped_success, scoped_failure (based on D language). This could be possible updating std::uncaught_exception or any other mechanism so that failure or success conditions can be determined.

That's the thing: if it's going to happen, I would much rather it be a language feature. Just compare the code:

auto &&log_status_on_exit = std::scope_exit([&status, &modifier]() ->void
{
 
LogStatus(status, "SomeFunction", modifier);
});

vs:

scope(exit)
{
 
LogStatus(status, "SomeFunction", modifier);
}

Isn't that so much better? No messy variables to create. No name collisions. No lambdas, and therefore no need to explicitly include anything in scope. It's immediately clear what it does.

That way, you don't need to modify std::uncaught_exception or anything like that. The system will call the `scope(failure)` at the correct time.

Of course the language integrated feature is more succinct and readable, but having an orthogonal design has also its advantages. BTW, I don't master C++ lambdas, but could you example be written as

auto && _ = std::scope_exit(
[
]() {

 
LogStatus(status, "SomeFunction", modifier);
});

?

I'm not a compiler writer, and I can not evaluate the cost of introducing such a feature compared to the introduction of a updated uncaught_exception. If the implementation on the language could provide much better performances this could be a valid justification.

Note also that if you propose to have scoped(success) and scoped(failure) this will introduces 4 new keywords. Some of them been already used as functions and the others are quite current words.

On the other side, providing a simple mechanism to determine success or failure has other applications. But maybe this merits a separated proposal. Is anyone working already on a proposal addressing this feature?

Best,
Vicente

Vicente J. Botet Escriba

unread,
Nov 13, 2012, 5:00:25 PM11/13/12
to std-pr...@isocpp.org
Le 13/11/12 19:11, Nicol Bolas a écrit :


On Tuesday, November 13, 2012 10:02:09 AM UTC-8, viboes wrote:
Le 13/11/12 18:47, Nicol Bolas a écrit :
I'm pretty sure he's talking about using RAII as the thing that increments the loop counter. Like this:

int *currValue = &someArray[0];
for(int i = 0; i < size; ++i)
{
  scope
(exit) {++currValue;} //Every iteration will update the current value.
 
//...
 
if(soemthing)
 
{
   
continue;
 
}

 
//++currValue; //old way: skipped by the continue. By accident.
}


Hi,

The C solution to this problem is to put the scoped exit action on the for, isn't it?

int *currValue = &someArray[0];
for(int i = 0; i < size; ++i,++currValue)
{
  //...
 
if(soemthing)
 
{
   
continue;
 
}
}



As I said, it would be used for more complex incrementing than something that could fit in a for-loop expression (or for non-for loops).
--
 
You are surely right it could have a sense to use a scoped exit in this context, but then the example should include a non-for loop and/or a more complex increment or just remove it as it doesn't add nothing more.

Vicente

Andrew Sandoval

unread,
Nov 13, 2012, 5:44:29 PM11/13/12
to std-pr...@isocpp.org
This is a good point.  I'm looking at it from a different perspective, which is that I'd also like to be able to use this in situations where exceptions can't be handled, such as in Windows kernel drivers.  If you were to use a vector there, you would have to replace the default allocator with one that pulled from the proper pool, and then you'd also have to ensure it didn't throw.  Every vector's constructor, resize, and push_back, etc. would then have to be checked, using something like: if(vec.size() != requested_size)...  Using STL in kernel drivers isn't common obviously, but it is still better to have to do all of those checks than to not have STL at all.

I think though that we can accomplish both objectives.  It shouldn't be too hard to add a non-type (bool) template parameter that enables throwing when a no-delete value is assigned in the constructor, operator=, or reset.  Then the only thing that has to change is the generator (the make_ function, which is currently scope_exit()).  In that way we can produce the throw and nothrow varieties as needed.  scoped_resource_t would have to have another non-type parameter too (bool), but I think that would be okay.  We might have to argue over the default value for it though.

Does that seem like a reasonable compromise?
 
> In my experience, every time an exception handler must be used, nesting and
> complexity increase, and often unnecessarily.

I have the completely opposite experience. I don't have to do nested checks
with exceptions, and the complexity of code is less than without exceptions.
I can decide where I put the complexity, and the no-error paths are fast and
clean because there's no checking of return values all over the place. That's
the reason people use RAII instead of return values or valid() checks.

>> Should we have operator-> for accessing the underlying value?
> I'm not opposed to that.  I can add that if there is consensus.  With the
> get() method, and a cast operator it doesn't feel intuitive to use
> operator-> to me, but I could be convinced.

When I use a handle, I want to use it for all accesses to the
underlying resource,
so if I have to call a member function of the underlying resource, I expect to
be able to do handle->func(), not handle.get().func() or, heaven forbid,
Resource& res = handle; res.func(); or Resource& res = handle.get(); res.func();
Handles are, as often as possible, as transparent as possible. That
would indicate
that we want to have, for the purposes of type-deducing function templates,
operator*(), and for things that want a pointer, operator&(), too.

I see your point there as well, and I can add operator* and operator&, and operator->.

Thank you!
-Andrew Sandoval

Ville Voutilainen

unread,
Nov 13, 2012, 5:47:07 PM11/13/12
to std-pr...@isocpp.org
On 14 November 2012 00:44, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> I think though that we can accomplish both objectives. It shouldn't be too
> hard to add a non-type (bool) template parameter that enables throwing when
> a no-delete value is assigned in the constructor, operator=, or reset. Then
> the only thing that has to change is the generator (the make_ function,
> which is currently scope_exit()). In that way we can produce the throw and
> nothrow varieties as needed. scoped_resource_t would have to have another
> non-type parameter too (bool), but I think that would be okay. We might
> have to argue over the default value for it though.
> Does that seem like a reasonable compromise?

Sounds reasonable, yes.

> I see your point there as well, and I can add operator* and operator&, and
> operator->.

Excellent!

Nevin Liber

unread,
Nov 13, 2012, 6:43:06 PM11/13/12
to std-pr...@isocpp.org
On 13 November 2012 11:26, Andrew Sandoval <sand...@netwaysglobal.com> wrote:

I'm very appreciative of the feedback from many others that have helped move this along.  I will try to find ways to make the examples more convincing, and I appreciate your suggestion.  Please however, if you are on the committee and intend to shoot this down no matter what, please make that clear.  This is a very time consuming process, and I don't have any to waste.

I'm on the committee, and I don't plan on shooting this down.  Heck, I could have used it today...

Real world case:  I added an element to a map.  I then did a bunch of work, and if any of that work fails (some times by return code; other times by exceptions being thrown), I need to remove that element from the map.  It would have been nice to have a scoped_exit for this.

(Yes, there are other ways I could have arranged the code, but I need to optimize it for the success case.)

Andrew Sandoval

unread,
Nov 14, 2012, 12:41:50 AM11/14/12
to std-pr...@isocpp.org

I'm in the process of reworking this.  I've added the options for throw/nothrow and the new operators.  I'll post an update once I have tested the reference implementation, and updated the proposal to match.

I've also changed some names, which I hope will make it less confusing.  I changed scoped_resource_uc to scoped_resource_unchecked.

I also changed scoped_resource_t to unique_resource, because it is in many ways very similar to unique_ptr, but more importantly because it needs distinction from scoped_function, scoped_resource_unchecked, and scoped_resource.  I couldn't think of a better way to express the idea that it uses compile-time constants for the deleter function and the no-delete value, where as scoped_resource_unchecked and scoped_resource do not.

That leads to another question...  Does it makes sense to change the generator (function template) for these from:
scoped_function<T> scope_exit(T t);  to
scoped_function<T> make_scoped_function(T t);
and from:
scoped_resource_unchecked<T, R> scope_exit(R r, T t);  to 
scoped_resource_unchecked<T, R> make_scoped_resource_unchecked(R r, T t);

and from:
scoped_resource<T, R, true> scope_exit(R r, T t, R nd);  to
scoped_resource<T, R, true> make_scoped_resource(R r, T t, R nd);
along with:
scoped_resource<T, R, false> scope_exit_nothrow(R r, T t, R nd);  to
scoped_resource<T, R, false> make_scoped_resource_nothrow(R r, T t, R nd);
?

And if so, should the header remain <scope_exit>?

Thank you all for feedback on this!
-Andrew Sandoval

Andrew Sandoval

unread,
Nov 17, 2012, 1:01:37 AM11/17/12
to std-pr...@isocpp.org
On Tuesday, November 13, 2012 4:47:08 PM UTC-6, Ville Voutilainen wrote:

Okay, I've updated the reference implementation to include these operators and the option of throwing exception on validity failure.  I've also changed all of the "generator" function templates to more closely follow the standard, so we now have make_scoped_function(), and make_scoped_resource().

I've updated draft of the proposal.  It along with the links to the reference implementation are still at http://www.andrewlsandoval.com/scope_exit/.

I will soon begin trying to fill in the Technical Details.  Prior to that any additional feedback on the reference implementation or the proposal text will be appreciated.

NOTE: The reference implementation requires std::remove_pointer and std::add_reference for the operator*.  When I tried compiling with g++ 4.7.2 on Windows there were compiler errors related to these type_traits.  (Or maybe I just did something brain-dead.)  I'll try to resolve that in the morning, but if anyone has experience with the differences in type_traits implementations and spots the problem before I do please let me know.  Otherwise thank you for being patient.  It does compile fine with VS 2010.

Thanks Much,
Andrew Sandoval

Vicente J. Botet Escriba

unread,
Nov 17, 2012, 4:20:39 AM11/17/12
to std-pr...@isocpp.org
Le 17/11/12 07:01, Andrew Sandoval a écrit :


Okay, I've updated the reference implementation to include these operators and the option of throwing exception on validity failure.  I've also changed all of the "generator" function templates to more closely follow the standard, so we now have make_scoped_function(), and make_scoped_resource().

I've updated draft of the proposal.  It along with the links to the reference implementation are still at http://www.andrewlsandoval.com/scope_exit/.

Hi,

I have made some previous comments that had no answer. One of my comments has already an answer, but not the others. Please, could you replay to the feedback I give you on my previous post?

Thanks,
Vicente

Andrew Sandoval

unread,
Nov 17, 2012, 11:11:36 AM11/17/12
to std-pr...@isocpp.org
Answers inline...


On Tuesday, November 13, 2012 1:30:53 PM UTC-6, viboes wrote:
Le 13/11/12 08:48, Andrew Sandoval a écrit :

Hi,

I don't think you need to add on your proposal the types returned by the scoped_exit function. Just propose a function that returns an implementation defined type that provides a specific behavior.
I'm open to the idea that the implementation details can be implementation defined, but I don't see this example anywhere in the rest of the Standard Library.  The library defines types and make_... functions for them, but doesn't just define make_... functions as far as I can tell.

I've made this easier in the updated reference implementation and proposal by using names that clearly distinguish the types, and "generator" function templates that use the make_... convention.


I'm for the first overloading, which corresponds to the interface Boost.ScopedExit has to make code portable.
I'm sorry, I don't understand what you are saying there.

The example motivating the second overload not convince me as we can use the simpler scoped_exit overload

Instead of
void drive_any_available_bus()
{
    auto &&bus = std::scope_exit(lock_available_bus(), unlock_bus);     // Always unlock the bus when done driving...  No need to verify the resource first
    if(bus == -1)
    {
        return;     // No buses...
    }
    drive(bus);
}
the following should do the same
void drive_any_available_bus()
{
    if(lock_available_bus() == -1) return;
    auto &&bus = std::scope_exit(unlock_bus);
drive(bus); }
Of course this will work, and this may be the best thing to do in certain cases, but if you code defensively you will bind the resource allocation to it's destruction, so that later on someone can't come in and add code that could throw an exception (for example) in between your allocation and your std::scope_exit() call.
 
The same applies to the third overload

// Open a file, ensure it is closed when we leave scope, if the file descriptor is valid...
auto&& iFileDesc = std::scope_exit(_open(pszPath, O_WRONLY), _close, -1);		// if _open returns -1, don't call _close...
if(iFileDesc.valid())
{
    _write(iFileDesc, &data, sizeof(data));
}

could be replaced by


    
auto fd = _open(pszPath, O_WRONLY);
if (fd != -1) return; // ...
auto&& guard = std::scope_exit(  [&fd] { _close(fd); } );
_write(iFileDesc, &data, sizeof(data));

Again, the same answer.  Code it defensively.  Don't assume that because this example is so simple it will never change.  In real world code it is very likely that there will be other things happening that could be moved between your allocation (the call to _open) and the construction of guard.  And it is even more likely that there will be code prior to the _open that does validation and constructs resources that will also need clean-up.  By using RAII wrappers throughout you have the liberty of using the quick-exit returns throughout the function.

Also, why declare a second named variable (guard), that never gets referenced?  Bind the resource to its deleter-function and use it naturally throughout the code, without thinking again about it's clean-up.


      
The last example concerns a resource wrapper with implicit conversion to the underlying resource. One of the problems with implicit conversions is that some overloads can be missed.
When wrapping a resource an alternative approach is to provide the specific functions, in your case define a File wrapper, but there is no need for a generic scoped_resource to implement it.

In particular the example could be implemented as
class TestClassMember3
{
private:
    FILE m_hEvent;
public:
    TestClassMember2() : m_hEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr))
    {
    }

    ~TestClassMember() 
    {
        CloseHandle(m_hEvent);
    } 
    // Other stuff here...
};
Again, in all of these examples you are breaking the tight coupling of the resource acquisition to its destruction.  By using std::unique_resource or std::scoped_resource as a class member variable, you ensure that destruction of the resource occurs in the proper order (the reverse order of initialization).  You can eliminate the explicit destructor, which might be written unintentionally to clean-up out-of-order.

The goal here is to get you to change your whole paradigm.  If you plan and declare the resource's lifetime when you initialize it or when you declare it (as is the case with std::unique_resource), you need not worry about remembering to put it in the destructor.  It's easy to remember in a class as simple as TestClassMember2, but in real world code there will be much more to it, and the fact is that these things are forgotten all of the time.

std::unique_resource and std::scoped_resource also build in the validity checking.  You can either call .valid() or you can use the throw option to throw failed_resource_initialization on construction of the wrapper.
 
Resuming the simple scoped_exit is enough to make a useful C++14 proposal that covers all the examples you have on your current proposal and could have good chances to be accepted.
Of course this doesn't covers all the classes you wanted to propose, but ...
Again, I'm not following what you are trying to say here.  I've read it a couple of times and I must be missing something.
 

The proposal could be complemented with scoped_success, scoped_failure (based on D language). This could be possible updating std::uncaught_exception or any other mechanism so that failure or success conditions can be determined.

That was added using the option to throw an exception on allocation / init failure.  It needs to be optional however to cover the broadest use cases, such as environment where exceptions can't be tolerated.

 
Just my 2 euro cents,
Vicente

 
Thanks Vicente.  I hope I've answered your questions well.
-Andrew Sandoval

Vicente J. Botet Escriba

unread,
Nov 17, 2012, 12:24:53 PM11/17/12
to std-pr...@isocpp.org
Le 17/11/12 17:11, Andrew Sandoval a écrit :
Answers inline...

On Tuesday, November 13, 2012 1:30:53 PM UTC-6, viboes wrote:
Le 13/11/12 08:48, Andrew Sandoval a écrit :

Hi,

I don't think you need to add on your proposal the types returned by the scoped_exit function. Just propose a function that returns an implementation defined type that provides a specific behavior.
I'm open to the idea that the implementation details can be implementation defined, but I don't see this example anywhere in the rest of the Standard Library.  The library defines types and make_... functions for them, but doesn't just define make_... functions as far as I can tell.
std::bind ?


I've made this easier in the updated reference implementation and proposal by using names that clearly distinguish the types, and "generator" function templates that use the make_... convention.


I'm for the first overloading, which corresponds to the interface Boost.ScopedExit has to make code portable.
I'm sorry, I don't understand what you are saying there.
I meant the the first overload of make_scoped_exit, which now corresponds to make_scoped_function.

The example motivating the second overload not convince me as we can use the simpler scoped_exit overload

Instead of
void drive_any_available_bus()
{
    auto &&bus = std::scope_exit(lock_available_bus(), unlock_bus);     // Always unlock the bus when done driving...  No need to verify the resource first
    if(bus == -1)
    {
        return;     // No buses...
    }
    drive(bus);
}
the following should do the same
void drive_any_available_bus()
{
    if(lock_available_bus() == -1) return;
    auto &&bus = std::scope_exit(unlock_bus);
drive(bus); }
Of course this will work, and this may be the best thing to do in certain cases, but if you code defensively you will bind the resource allocation to it's destruction, so that later on someone can't come in and add code that could throw an exception (for example) in between your allocation and your std::scope_exit() call.
I can understand the need for a wrapper that creates the resource on construction and release them on destruction. I just think the generic wrapper is not worth. At least your example should be much more explicit and from the real world.

 
The same applies to the third overload

// Open a file, ensure it is closed when we leave scope, if the file descriptor is valid...
auto&& iFileDesc = std::scope_exit(_open(pszPath, O_WRONLY), _close, -1);		// if _open returns -1, don't call _close...
if(iFileDesc.valid())
{
    _write(iFileDesc, &data, sizeof(data));
}

could be replaced by

auto fd = _open(pszPath, O_WRONLY);
if (fd != -1) return; // ...
auto&& guard = std::scope_exit(  [&fd] { _close(fd); } );
_write(iFileDesc, &data, sizeof(data));

Again, the same answer.  Code it defensively.  Don't assume that because this example is so simple it will never change.  In real world code it is very likely that there will be other things happening that could be moved between your allocation (the call to _open) and the construction of guard.  And it is even more likely that there will be code prior to the _open that does validation and constructs resources that will also need clean-up.  By using RAII wrappers throughout you have the liberty of using the quick-exit returns throughout the function.

What prevents the user of allocating the resource outside of the scoped exit, as e.g. in

auto&& fd = _open(pszPath, O_WRONLY);

// do other things as throw an exception
auto&& iFileDesc = std::scope_exit(fd, _close, -1); if(iFileDesc.valid()) { _write(iFileDesc, &data, sizeof(data)); }

This is why I think that the wrapper must be specific

{
  File f(pszPath, O_WRONLY);
  // ...
} // close called on destruction




Also, why declare a second named variable (guard), that never gets referenced?  Bind the resource to its deleter-function and use it naturally throughout the code, without thinking again about it's clean-up.
I would like to see a way to using anonymous variables

auto&& ... = std::scope_exit(  [&fd] { _close(fd); } );

or  
auto&& auto = std::scope_exit(  [&fd] { _close(fd); } );
But this is a minor issue any way.

The last example concerns a resource wrapper with implicit conversion to the underlying resource. One of the problems with implicit conversions is that some overloads can be missed.
When wrapping a resource an alternative approach is to provide the specific functions, in your case define a File wrapper, but there is no need for a generic scoped_resource to implement it.

In particular the example could be implemented as
class TestClassMember3
{
private:
    FILE m_hEvent;
public:
    TestClassMember2() : m_hEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr))
    {
    }

    ~TestClassMember() 
    {
        CloseHandle(m_hEvent);
    } 
    // Other stuff here...
};
Again, in all of these examples you are breaking the tight coupling of the resource acquisition to its destruction. 
Why? Here the class TestClassMemeber is playing the role of a specif wrapper.


By using std::unique_resource or std::scoped_resource as a class member variable, you ensure that destruction of the resource occurs in the proper order (the reverse order of initialization).  You can eliminate the explicit destructor, which might be written unintentionally to clean-up out-of-order.
I understand that it is the role of the specific wrapper to release the resource :)


The goal here is to get you to change your whole paradigm.  If you plan and declare the resource's lifetime when you initialize it or when you declare it (as is the case with std::unique_resource), you need not worry about remembering to put it in the destructor.  It's easy to remember in a class as simple as TestClassMember2, but in real world code there will be much more to it, and the fact is that these things are forgotten all of the time.
I don't think the generic classes you are proposing make the user to change to the best 'paradigm'. I really think the user needs specific wrappers, not these kind of generic raii classes that let him the responsibility to declare an auxiliary class to release the resource. A specific C++ class depending on the domain must be defined to avoid the user been working with non-safe resources. Well, this is just my opinion. 

std::unique_resource and std::scoped_resource also build in the validity checking.  You can either call .valid() or you can use the throw option to throw failed_resource_initialization on construction of the wrapper.
 
Resuming the simple scoped_exit is enough to make a useful C++14 proposal that covers all the examples you have on your current proposal and could have good chances to be accepted.
Of course this doesn't covers all the classes you wanted to propose, but ...
Again, I'm not following what you are trying to say here.  I've read it a couple of times and I must be missing something.
I meant that IMO only the fist overload is useful. The resource abstractions are not. Proposing th scoped_exit function has a chance to be accepted.

 

The proposal could be complemented with scoped_success, scoped_failure (based on D language). This could be possible updating std::uncaught_exception or any other mechanism so that failure or success conditions can be determined.

That was added using the option to throw an exception on allocation / init failure.  It needs to be optional however to cover the broadest use cases, such as environment where exceptions can't be tolerated.
I think you have not understood my concern. What you have added lastly has nothing to be with scoped(success) scoped(failure). See the D language documentation.

 
Thanks Vicente.  I hope I've answered your questions well.
Now you did.
Vicente

Andrew Sandoval

unread,
Nov 17, 2012, 4:30:42 PM11/17/12
to std-pr...@isocpp.org
On Saturday, November 17, 2012 11:24:56 AM UTC-6, viboes wrote:
Le 17/11/12 17:11, Andrew Sandoval a écrit :
Answers inline...

On Tuesday, November 13, 2012 1:30:53 PM UTC-6, viboes wrote:
Le 13/11/12 08:48, Andrew Sandoval a écrit :

Hi,

I don't think you need to add on your proposal the types returned by the scoped_exit function. Just propose a function that returns an implementation defined type that provides a specific behavior.
I'm open to the idea that the implementation details can be implementation defined, but I don't see this example anywhere in the rest of the Standard Library.  The library defines types and make_... functions for them, but doesn't just define make_... functions as far as I can tell.
std::bind ?
True enough, and if the committee, or if others feel strongly that this approach is better I am willing -- I don't mind it at all.  However, it only works for scoped_function, scoped_resource_unchecked and scoped_resource.  I don't see any way that it can work for the much tighter, and IMO generally more useful unique_resource.

I've made this easier in the updated reference implementation and proposal by using names that clearly distinguish the types, and "generator" function templates that use the make_... convention.


I'm for the first overloading, which corresponds to the interface Boost.ScopedExit has to make code portable.
I'm sorry, I don't understand what you are saying there.
I meant the the first overload of make_scoped_exit, which now corresponds to make_scoped_function.
Okay...  Your statement makes more sense now.  I had to slow down and insert commas before I understood what you were saying.  Thanks for clarifying.

The example motivating the second overload not convince me as we can use the simpler scoped_exit overload

Instead of
void drive_any_available_bus()
{
    auto &&bus = std::scope_exit(lock_available_bus(), unlock_bus);     // Always unlock the bus when done driving...  No need to verify the resource first
    if(bus == -1)
    {
        return;     // No buses...
    }
    drive(bus);
}
the following should do the same
void drive_any_available_bus()
{
    if(lock_available_bus() == -1) return;
    auto &&bus = std::scope_exit(unlock_bus);
drive(bus); }
Of course this will work, and this may be the best thing to do in certain cases, but if you code defensively you will bind the resource allocation to it's destruction, so that later on someone can't come in and add code that could throw an exception (for example) in between your allocation and your std::scope_exit() call.
I can understand the need for a wrapper that creates the resource on construction and release them on destruction. I just think the generic wrapper is not worth. At least your example should be much more explicit and from the real world.

Thanks, I will work on a better, more real-world example.  
 
The same applies to the third overload

// Open a file, ensure it is closed when we leave scope, if the file descriptor is valid...
auto&& iFileDesc = std::scope_exit(_open(pszPath, O_WRONLY), _close, -1);		// if _open returns -1, don't call _close...
if(iFileDesc.valid())
{
    _write(iFileDesc, &data, sizeof(data));
}

could be replaced by

auto fd = _open(pszPath, O_WRONLY);
if (fd != -1) return; // ...
auto&& guard = std::scope_exit(  [&fd] { _close(fd); } );
_write(iFileDesc, &data, sizeof(data));

Again, the same answer.  Code it defensively.  Don't assume that because this example is so simple it will never change.  In real world code it is very likely that there will be other things happening that could be moved between your allocation (the call to _open) and the construction of guard.  And it is even more likely that there will be code prior to the _open that does validation and constructs resources that will also need clean-up.  By using RAII wrappers throughout you have the liberty of using the quick-exit returns throughout the function.

What prevents the user of allocating the resource outside of the scoped exit, as e.g. in

auto&& fd = _open(pszPath, O_WRONLY);

// do other things as throw an exception
auto&& iFileDesc = std::scope_exit(fd, _close, -1); if(iFileDesc.valid()) { _write(iFileDesc, &data, sizeof(data)); }

Nothing does.  But then nothing stops you from doing this either:

SomeClass *pInstance = new SomeClass();
.
// Do stuff with pInstance
.
.
std
::unique_ptr<SomeClass> pSomeClass(pInstance);

And yet we still have std::unique_ptr, etc.

This is why I think that the wrapper must be specific

{
  File f(pszPath, O_WRONLY);
  // ...
} // close called on destruction


I don't think we are going to agree on that.  I would still do my File class like this:
class File
{
private:
    std
::unique_resource<UNIQUE_DELETER(close), -1> m_iFd;
public:
   
File(const char *pszPath, int oflags, int iCreateFlags) : m_iFd(open(pszPath, oflags, iCreateFlags))
   
{
   
}
   
.
   
.
   
.
};

Because again, I don't need an explicit destructor, I guarantee the proper order of destruction, etc.

Also, why declare a second named variable (guard), that never gets referenced?  Bind the resource to its deleter-function and use it naturally throughout the code, without thinking again about it's clean-up.
I would like to see a way to using anonymous variables

auto&& ... = std::scope_exit(  [&fd] { _close(fd); } );

or  
auto&& auto = std::scope_exit(  [&fd] { _close(fd); } );
But this is a minor issue any way.
That is a different battle / proposal.  I'm not opposed to it, but it has to be fought on its own.
     

The last example concerns a resource wrapper with implicit conversion to the underlying resource. One of the problems with implicit conversions is that some overloads can be missed.
When wrapping a resource an alternative approach is to provide the specific functions, in your case define a File wrapper, but there is no need for a generic scoped_resource to implement it.

In particular the example could be implemented as
class TestClassMember3
{
private:
    FILE m_hEvent;
public:
    TestClassMember2() : m_hEvent(CreateEvent(nullptr, TRUE, FALSE, nullptr))
    {
    }

    ~TestClassMember() 
    {
        CloseHandle(m_hEvent);
    } 
    // Other stuff here...
};
Again, in all of these examples you are breaking the tight coupling of the resource acquisition to its destruction. 
Why? Here the class TestClassMemeber is playing the role of a specif wrapper.
 
Because in reality it will probably do more than that.  And even if it does not, by File example has less risk of someone screwing it up.

By using std::unique_resource or std::scoped_resource as a class member variable, you ensure that destruction of the resource occurs in the proper order (the reverse order of initialization).  You can eliminate the explicit destructor, which might be written unintentionally to clean-up out-of-order.
I understand that it is the role of the specific wrapper to release the resource :)

The goal here is to get you to change your whole paradigm.  If you plan and declare the resource's lifetime when you initialize it or when you declare it (as is the case with std::unique_resource), you need not worry about remembering to put it in the destructor.  It's easy to remember in a class as simple as TestClassMember2, but in real world code there will be much more to it, and the fact is that these things are forgotten all of the time.
I don't think the generic classes you are proposing make the user to change to the best 'paradigm'. I really think the user needs specific wrappers, not these kind of generic raii classes that let him the responsibility to declare an auxiliary class to release the resource. A specific C++ class depending on the domain must be defined to avoid the user been working with non-safe resources. Well, this is just my opinion. 

Agreed, that is your opinion and you are entitled to it.  I just have to ask, were you equal opposed to std::unique_ptr and std::shared_ptr, because class could've been written with destructors that clean-up pointer too...?

std::unique_resource and std::scoped_resource also build in the validity checking.  You can either call .valid() or you can use the throw option to throw failed_resource_initialization on construction of the wrapper.
 
Resuming the simple scoped_exit is enough to make a useful C++14 proposal that covers all the examples you have on your current proposal and could have good chances to be accepted.
Of course this doesn't covers all the classes you wanted to propose, but ...
Again, I'm not following what you are trying to say here.  I've read it a couple of times and I must be missing something.
I meant that IMO only the fist overload is useful. The resource abstractions are not. Proposing th scoped_exit function has a chance to be accepted.

Okay, thanks.  If the rest has no chance, I'm not hearing that from others.
 

The proposal could be complemented with scoped_success, scoped_failure (based on D language). This could be possible updating std::uncaught_exception or any other mechanism so that failure or success conditions can be determined.

That was added using the option to throw an exception on allocation / init failure.  It needs to be optional however to cover the broadest use cases, such as environment where exceptions can't be tolerated.
I think you have not understood my concern. What you have added lastly has nothing to be with scoped(success) scoped(failure). See the D language documentation.

Okay, too many languages in my head to have to worry about D too.
 
Thanks Vicente.  I hope I've answered your questions well.
Now you did.
Vicente

Thanks,
Andrew Sandoval

Vicente J. Botet Escriba

unread,
Nov 18, 2012, 12:30:26 PM11/18/12
to std-pr...@isocpp.org
Le 17/11/12 07:01, Andrew Sandoval a écrit :
Okay, I've updated the reference implementation to include these operators and the option of throwing exception on validity failure.  I've also changed all of the "generator" function templates to more closely follow the standard, so we now have make_scoped_function(), and make_scoped_resource().

I've updated draft of the proposal.  It along with the links to the reference implementation are still at http://www.andrewlsandoval.com/scope_exit/.

Hi Andrew,

I've been thinking more about your proposal and I would like to share with you an alternative design.

First we could start by defining the Resource requirements. Once we have the Resource concept we can build on top of it the scoped_resource, unique_resource and why not shared_resource.

IIUC the resources you are talking of are represented by builtin types, either a pointer or an integral type, so I think that we can consider that the underlying type is a regular type with an invalid value.

Resource requirements

   R is a model of Resource if R is Regular and the following requirements are satisfied where
   R r;
   R::value_type v;
 
   R::value_type
The type of the resource that shall be Regular also.

   R::invalid
the invalid value

   R()
Constructs an invalid resource.

   R(v)
Constructs a resource with a specific value.

   r.release()
Release the resource


Next follows an example of a generic resource using static information (as the one used by unique_resource)

  template <typename R, R Invalid, void(*Release)(R)>
  struct resource
  {
    typedef T value_type;
    static constexpr value_type invalid() {return Invalid};
    value_type value;
    resource() noexcept : value(invalid()) {}
    resource(value_type val) noexcept : value(val) {}
    void release() {
        Release(value);
    };
  };

The user could define specific resources, e.g. a resource representing a file descriptor

  typedef resource<int, -1, _close> fd;

or a specific structure

  struct fd : resource<int, -1, _close>
  {
    typedef resource<int, -1, _close> base_type;
    fd() noexcept : base_type() {}
    fd(value_type val) noexcept : base_type(val) {}
  };

Then the user could add some specific factories which could take care of whether the resource construction shall throw or not

  fd make_fd(fd::value_type val)
  {
    if (val == fd::invalid) return fd(val);
    else throw std::failed_resource_initialization;
  }
  fd make_fd(std::nothrow_t, fd::value_type val) noexcept
  {
    return fd(val);
  }

Note that while this mean some effort from the user side, this should be done only once. Do you find this reasonable?

Now we can define a simple scoped resource as follows

  template<typename R>
  class scoped_resource  {
    R resource_;
  public:
    explicit scoped_resource(R resource) : resource_(resource) {}
    bool valid() const noexcept {
      return resource_.value != R::invalid();
    }
    ~scoped_resource() {
      if (valid()) resource_.release();
    }
    typename R::value_type get() const noexcept {
      return resource_.value;
    }
    operator typename R::value_type() const noexcept  {
      return get();
    }
  };
 
The user can use this as follows

void test_scoped_resource_uref_resfail_throw()
{
  try  {
    auto&& afdg = std::make_scoped_resource(make_fd(_open("C:\\temp\\test4ndx.txt", O_WRONLY)));
    _write(afdg, "Test4FuncIntNdFail\r\n", 20);
  }
  catch (const std::failed_resource_initialization &e)  {
    std::cout << "Exception: " << e.what() << std::endl;
  }

void test_scoped_resource_uref_resfail() {
  auto&& afdg = std::make_scoped_resource(make_fd(std::nothrow, _open("C:\\temp\\test4ndx.txt", O_WRONLY)));
  // _open will return -1, so don't close...
  if(afdg.valid())   {
    _write(afdg, "test_scoped_resource_uref_resfail\r\n", 20);
  }
}


Note that here we don't see any mention of what is the invalid value, nor what is the deleter.

The user could also define specific scoped file descriptor factories

  scoped_resource<fd> make_scoped_fd(fd::value_type val)  {
    return make_scoped_resource(make_fd(val));
  }
  scoped_resource<fd> make_fd(std::nothrow_t, fd::value_type val)  {
    return make_scoped_resource(make_fd(std::nothrow, val));
  }   

and use them as in

void test_scoped_resource_uref_resfail_throw() {
  try {
    auto&& afdg = make_scoped_fd(_open("C:\\temp\\test4ndx.txt", O_WRONLY));
    _write(afdg, "Test4FuncIntNdFail\r\n", 20);
  }  catch (const std::failed_resource_initialization &e)   {
    std::cout << "Exception: " << e.what() << std::endl;
  }
}

void test_scoped_resource_uref_resfail() {
  auto&& afdg = std::make_scoped_fd(std::nothrow, _open("C:\\temp\\test4ndx.txt", O_WRONLY));
  // _open will return -1, so don't close...
  if(afdg.valid())   {
    _write(afdg, "test_scoped_resource_uref_resfail\r\n", 20);
  }
}

unique_resource could be defined based on your current implementation with some minor changes.

And last it would be really great if we could have shared_resource template class based on shared_ptr.

What do you think of this alternative design? Does it covers all your needs?

Best,
Vicente

Ville Voutilainen

unread,
Nov 18, 2012, 12:37:30 PM11/18/12
to std-pr...@isocpp.org
On 18 November 2012 19:30, Vicente J. Botet Escriba
<vicent...@wanadoo.fr> wrote:
> Resource requirements
> R is a model of Resource if R is Regular and the following requirements
> are satisfied where
> R r;
> R::value_type v;
>
> R::value_type
> The type of the resource that shall be Regular also.
>
> R::invalid
> the invalid value
>
> R()
> Constructs an invalid resource.
>
> R(v)
> Constructs a resource with a specific value.
>
> r.release()
> Release the resource
>
>
> Next follows an example of a generic resource using static information (as
> the one used by unique_resource)
>
> template <typename R, R Invalid, void(*Release)(R)>

That won't work for quite many resource types. To be able to use a resource
as a non-type template parameter, we place quite stringent limitations
on what a resource can be. Class types for example are right out.

Ville Voutilainen

unread,
Nov 18, 2012, 12:52:40 PM11/18/12
to std-pr...@isocpp.org
On 17 November 2012 08:01, Andrew Sandoval <sand...@netwaysglobal.com> wrote:
> Okay, I've updated the reference implementation to include these operators
> and the option of throwing exception on validity failure. I've also changed
> all of the "generator" function templates to more closely follow the
> standard, so we now have make_scoped_function(), and make_scoped_resource().
> I've updated draft of the proposal. It along with the links to the
> reference implementation are still at
> http://www.andrewlsandoval.com/scope_exit/.
> I will soon begin trying to fill in the Technical Details. Prior to that
> any additional feedback on the reference implementation or the proposal text
> will be appreciated.
> NOTE: The reference implementation requires std::remove_pointer and
> std::add_reference for the operator*. When I tried compiling with g++ 4.7.2
> on Windows there were compiler errors related to these type_traits. (Or
> maybe I just did something brain-dead.) I'll try to resolve that in the
> morning, but if anyone has experience with the differences in type_traits
> implementations and spots the problem before I do please let me know.
> Otherwise thank you for being patient. It does compile fine with VS 2010.

So, let me try to recap my understanding:

1) scoped_function just enables running a cleanup functor. Given a lambda,
it provides the bare minimum possible. If lambdas are allowed to define
how their captures are initialized, we have a quite good resource wrapper
then. (I don't think this proposal needs to wait for lambdas to get the
capture-initializers).

2) scoped_resource_unchecked wraps a resource and its deleter, allows
accessing it
through the scoped_resource_unchecked, and handles eg. resetting.

3) scoped_resource further allows recognizing invalid values, and provides the
option to throw when initialized with an invalid value.

4) unique_resource is lighter-weight and saves space, because it
doesn't actually store a deleter
object.

Did I get it right so far?

Of these, I expect scoped_resource to be the "default", since it's the
most capable and most generic.
I have trouble with the name unique_resource, because I don't see it
being similar to unique_ptr and
unique_lock in the sense that those are move-only resource handles.
The most important facility
of unique_resource isn't uniqueness, but rather that it's a
space-optimized variant of scoped_resource,
and doesn't allow the same breadth of possible deleter types.

I think operator& and operator-> should return the same thing. It's
actually a bit hairy to support both
pointers and non-pointers in that space, especially with operator*.
Also, some pointers are a bit hairy
to begin with, like FILE*. I don't know how to solve that problem nicely.

Faisal Vali

unread,
Nov 18, 2012, 1:33:25 PM11/18/12
to std-pr...@isocpp.org
This might not be pertinent to this discussion or might already have
been discussed - so I apologize in advance if that is the case (and
also for not reading all the posts in this thread) but is anyone
familiar with 'D' s "scope" statement?

I have included a link to Andrei's Lang.Next Slides (the first 30 are
pertinent to this post) in which he attempts to demonstrate why he
thinks the D solution ('scope statement') is superior to RAII.

I am not proposing anything here, nor suggesting that a library
solution should be replaced by a language feature - I just thought
that our eventual solution to this problem might benefit from D's
perspective - and wanted to make sure that if anyone was interested in
exploring the solution space further, that this be on their radar.

thanks!
Faisal Vali
> --
>
>
>
AndreiLangNext-2012.pdf

Faisal Vali

unread,
Nov 18, 2012, 1:34:26 PM11/18/12
to std-pr...@isocpp.org
er sorry about the omission - here's the link to Andrei's slides -
http://ecn.channel9.msdn.com/events/LangNEXT2012/AndreiLangNext.pdf
Faisal Vali

Jens Maurer

unread,
Nov 18, 2012, 2:37:13 PM11/18/12
to std-pr...@isocpp.org
On 11/18/2012 07:33 PM, Faisal Vali wrote:
> I have included a link to Andrei's Lang.Next Slides (the first 30 are
> pertinent to this post) in which he attempts to demonstrate why he
> thinks the D solution ('scope statement') is superior to RAII.

Using C++ and lambdas and std::uncaught_exception(), I think we
should be able to get to the following, syntax-wise:

<action>
auto cleanup1 = make_cleanup( [&]() { <cleanup> } );
auto failure1 = make_failure( [&]() { <rollback> } );

and composition is as easy as in D.

The cleanup object's destructor would unconditionally execute the
lambda.
The failure object's destructor would only execute the lambda
if std::uncaught_exception() is true, i.e. the destructor
is invoked during stack unwinding.

Jens

Nicol Bolas

unread,
Nov 18, 2012, 2:57:39 PM11/18/12
to std-pr...@isocpp.org

That's not sufficient for the failure case. You only want the failure to happen if the destructor is being called because that particular scope is being unwound.

If you were to put one of these objects into a class's destructor (so that any stack unwinding from exceptions thrown within the destructor would be detected). But it would also detect stack unwinding due to exceptions thrown which caused that particular class's destructor to be fired. Just look at the link for an example.

Besides, how is this all not a giant kludge? If you want this feature, propose it as a real, legitimate language feature, not some hack with lambdas and such. Because if it goes in as a hack, when someone does want it in the core language, they'll just say, "Use the hack."

Ville Voutilainen

unread,
Nov 18, 2012, 3:12:15 PM11/18/12
to std-pr...@isocpp.org
On 18 November 2012 21:57, Nicol Bolas <jmck...@gmail.com> wrote:
> Besides, how is this all not a giant kludge? If you want this feature,
> propose it as a real, legitimate language feature, not some hack with
> lambdas and such. Because if it goes in as a hack, when someone does want it
> in the core language, they'll just say, "Use the hack."

What exactly do you mean by "this all"? Which part of the proposed library
facilities are "hacks"? So far I have seen zero reasons to add new language
features, the proposed library additions work just fine with the
existing language
facilities.

grigor...@gmail.com

unread,
Nov 18, 2012, 3:37:22 PM11/18/12
to std-pr...@isocpp.org
 
By the way, recently on boost mailing list Evgeny Panasyk proposed as additions to BOOST_SCOPE_EXIT scope_failure and scope_success macros.
 
 
They cannot be implemented in standard-compliant way. He relies on platform-specific functions. I guess that may be called 'a giand kludge'.
Interestingly BOOST_SCOPE_FAILURE may be implemented via lambda, but not BOOST_SCOPE_SUCCESS.
But I think both may be good additions as language features.
 
Regards,
Gregory
 
 

Nicol Bolas

unread,
Nov 18, 2012, 4:06:55 PM11/18/12
to std-pr...@isocpp.org


On Sunday, November 18, 2012 12:12:18 PM UTC-8, Ville Voutilainen wrote:
On 18 November 2012 21:57, Nicol Bolas <jmck...@gmail.com> wrote:
> Besides, how is this all not a giant kludge? If you want this feature,
> propose it as a real, legitimate language feature, not some hack with
> lambdas and such. Because if it goes in as a hack, when someone does want it
> in the core language, they'll just say, "Use the hack."

What exactly do you mean by "this all"? Which part of the proposed library
facilities are "hacks"?

Well, ignoring the issue I pointed out where it doesn't work, it's a kludge for the same reason that the safe-bool idiom is a kludge. It functions, but it's a giant wart in the middle of a piece of source code. The committee took steps to get rid of safe-bool, which required significant changes to the spec language with regard to conversions. It required adding this new concept of "contextual conversion to bool" as a back-door so that `explicit operator bool()` would work in places we want it to, but not in others.

The difference is that, where the safe-bool idiom could be tucked away in a class declaration, only seen by those who read through the declaration, this is going to be used everywhere.

It's a hack because it's easily and accidentally broken:

auto cleanup1 = make_cleanup( [&]() { <cleanup> } );
//Some code
auto cleanup1 = make_cleanup( [&]() { <more cleanup> } );

That's a compile error. This is not:

scope(exit) {<cleanup>}
//Some code
scope
(exit) {<more cleanup>}

Yes, it's a simple thing to fix. But do you honestly want a bunch of auto variable declarations, `std::make_cleanup` calls, and such cluttering up functions?

We already have to make a language change (whether directly or indirectly via a language-support-library change) to make the failure example actually work properly. If we're going to make a language change, let's not mess around and do it right.

Korcan Hussein

unread,
Nov 18, 2012, 8:58:26 PM11/18/12
to std-pr...@isocpp.org
This looks like a really convoluted solution, I think you can achieve more simple yet better solutions following the with-/backet style higher-order functions, e.g.:

#include <cstdio>
#include <stdexcept>

template < typename Aquire, typename Release, typename Action >
inline auto bracket(Aquire aquire, Release release, Action action) -> decltype(action(aquire()))
{
   
auto val = aquire();
   
try
   
{
       
auto result = action(val);
        release
(val);
       
return result;
   
}
   
catch (...)    
   
{
        release
(val);
       
throw ;
   
}
}

template < typename Action >
inline auto with_file(const char* filename, const char* mode, Action action) -> decltype(action(static_cast<FILE*>(nullptr)))
{
   
return bracket
   
(
       
[filename, mode]()
       
{
           
if (FILE* fp = std::fopen(filename, mode))
               
return fp;
           
else
               
throw std::runtime_error("Failed to open File.");
       
},
        std
::fclose,
        action
   
);
}

int main()
{
   
return with_file("foo.txt", "r", [](FILE* fp)
   
{
       
// ...
       
return EXIT_SUCCESS;
   
});
}

Jens Maurer

unread,
Nov 19, 2012, 2:55:42 AM11/19/12
to std-pr...@isocpp.org

Trivia note:
The "acquire" word needs to get a "c" in your code.
http://www.merriam-webster.com/dictionary/acquire

Jens

Jens Maurer

unread,
Nov 19, 2012, 3:00:48 AM11/19/12
to std-pr...@isocpp.org
On 11/18/2012 09:37 PM, grigor...@gmail.com wrote:
> By the way, recently on boost mailing list Evgeny Panasyk proposed as additions to BOOST_SCOPE_EXIT scope_failure and scope_success macros.
>
> http://boost.2283326.n4.nabble.com/scope-exit-D-style-scope-failure-and-scope-success-in-C-td4636441.html

I like the "destructor with bool parameter" approach a lot, instead of
another global state query function. This seems to be a small, but
(for the corner cases we're discussing here) useful core extension.

Jens

It is loading more messages.
0 new messages