GCC attribute cleanup is so much harder to misuse than some hodgepodge macro construction like this. If you're a C developer, it is by far the superior option. Clang supports it, of course.
It's also used in several important big Linux programs so it's unlikely to be going away. There have been efforts to get the mechanism added to the next C standard too.
Re: going away — has GCC ever removed any relatively common attribute in the past? They basically don't ever, to my knowledge. They haven't removed nested functions, for example, which would probably be a good idea. (To your point, though: important Linux programs like GRUB use nested functions.)
This is one I put together a while back for gcc and clang: https://github.com/jeffwalter/defer/. There's no limit to the number of defers you can have. At least not one I've run into.
You can define the macro as `return` -- it won't cause infinite recursion in the C pre-processor, but you do have to either use the `Deferral` macro in every function, or `#undef return` to do a plain return.
As I mentioned in another comment, it seems almost identical to Boost.ScopeExit, [0] but with the special restriction that it causes statements to run at the point a function returns. From the article: it does not care about lexical scopes.
edit Looking at the implementation, it seems to use setjmp/longjmp on some platforms. Nasty. I believe Boost.ScopeExit does some macro trickery to leverage C++'s RAII, which isn't so bad.
Or, arguably, the C++ equivalent is just the destructor. 'Defer' and 'ScopeExit' are interesting, but I'm not sure if I see the advantage over widely understood first-class language features. If you don't want to write a class just to get cleanup logic at scope exit, consider using std::unique_ptr with a custom deleter -- this construct is for managing resources in a general sense, not necessarily just memory allocation.
> arguably, the C++ equivalent is just the destructor
You'd have to define an empty class with a destructor, then instantiate the class at the appropriate place in your code. The point of Boost.ScopeExit is to handle that boilerplate for you.
> I'm not sure if I see the advantage over widely understood first-class language features
The advantage is simply reduced boilerplate. You're right that it's 'less standard' and more likely to baffle the reader, if they're not already familiar with ScopeExit.
> consider using std::unique_ptr with a custom deleter -- this construct is for managing resources in a general sense, not necessarily just memory allocation
This strikes me as hijacking a memory-management facility, using it for a different purpose than its intent. That's bad for readability. I'd prefer either ScopeExit or the dummy object pattern I described.
>You'd have to define an empty class with a destructor, then instantiate the class at the appropriate place in your code. The point of Boost.ScopeExit is to handle that boilerplate for you.
I would argue that every time you want to execute something on scope exit, that essentially models a resource or lock of some kind. Which means the constructor wouldn't be empty (it would acquire the resource), and writing the class wouldn't really be "boilerplate", it would be clean design.
>This strikes me as hijacking a memory-management facility, using it for a different purpose than its intent. That's bad for readability. I'd prefer either ScopeExit or the dummy object pattern I described.
I think it is much better for readability, because it usually models the thing you want to do much more precisely. Although it is a shame that only unique_ptr is standard and not unique_handle, but you could write that yourself relatively quickly. This case doesn't just model that you want to execute something at the end of a scope, but that you have a handle to a resource that you can std::move around and that will get destroyed at the appropriate time (if you model your data correctly).
> essentially models a resource or lock of some kind
I agree that proper RAII is generally the way to go. I think of ScopeExit as being for those situations where you have to deal with C-style code. One way is to wrap the C-style code in C++ and leverage C++'s RAII. The alternative is to write C-style code yourself, but you can still use ScopeExit to ensure you didn't miss any edge-cases where cleanup is necessary (multiple points of return, exceptions, etc).
In this way, ScopeExit is for 'C++ as a better C' programming.
> This case doesn't just model that you want to execute something at the end of a scope, but that you have a handle to a resource that you can std::move around and that will get destroyed at the appropriate time
This is going far further down the C++ rabbit-hole than what we started with, though. If I just want to be sure that I didn't miss any places where I need to call free (or similar) in my C-style code, ScopeExit is just the thing.
Agreed. "Defer" in Go isn't a great idea anyway. It's more what they were forced to, given a GC language without actions at scope exit.
(Callbacks from the garbage collector on deallocation are even worse. That way lies locking problems and "re-animation", a nightmare in Managed C++. One of the cleaner solutions to scope-based cleanup is the "with" clause in Python, which is based on "with" constructs in Common LISP. Only thing I've seen where exceptions in a destructor work right.)
Defer and errdefer in zig are pretty awesome, so I'm not convinced c couldn't use it, but zig has a very different error return model from C, and I tend to disapprove of hacky solutions.
I wrote on reddit[1] about why I prefer this over c++:
> It's not a technical problem, but a social problem. Yes, I would definitely prefer the c++ RAII (and refcounts would be nice too). If you say 'my project is in c++', that sends a certain message to prospective contributors, about what your priorities and ideals are. It can attract certain kinds of contributors and discourage others. Then you have the problem of how to define your subset of c++. It's easy to say 'no exceptions, no RTTI, no STL'. But there are subtler things. As you mention, templates are occasionally useful. But sometimes they're completely superfluous. Do you allow virtual functions? Multiple inheritance? The answer is almost invariably 'maybe'; you have to exercise taste. I can do that by myself, for my own project. But if I want to be able to accept contributions from others, I need a clearer set of contribution guidelines than 'wherever my whimsy takes me', and for such a purpose 'whatever the c compiler accepts' is the best I can do.
> Also, tcc is about 10x faster than gcc and clang, which makes development a joy.
I think most large scale projects, or those with special requirements (things like embedded systems) are defining what subset of C++ they are ok with. Here are some examples:
Large commercial projects. The problem is the mid sized projects, especially FOSS ones, there isn’t the time needed to curate every pull request and bikeshed. There is also something to be said for having less abstractions, moderately wet code can be easier to follow and fix than needlessly dry code.
Because the C++ compiler accepts an absolutely insane variety of code styles. I say this as someone who mainly writes C++. If I write my own project, or a project where I only have to work with a small number of people all of whom I know have extensive C++ experience, or a project that is so large that I can afford to consistently rewrite parts, educate contributors and maintain style guides, I choose C++. But anything that doesn't hit these bells, I'd rather use C (or something else). People mixing completely different styles in C++ is a nightmare.
> People mixing completely different styles in C++ is a nightmare.
This is really not my experience. Any decent-sized program will have parts more in a functional style, others in a more OOP one, others in regular-typed Stepanov bliss, others in template or constexpr metaprograms.... and this causes zero issues in practice once people get past their assumptions about what clean code should look like.
The compiler speed pain is real, and excessive use of templates to do compile time metaprogramming can be intractable, but really it's not the 2000s any more (+) and we should stop pretending that C++ is just C with classes; exceptions, RTTI and STL do actually work now and are portable.
(+) offer not valid on microcontrollers or proprietary C++ compilers
Why not use __attribute__((cleanup))? It's widely used in real code (eg. systemd has been using it for years) and there's effort going on to get the mechanism standardized in the next C standard.
Do people even use MSVC to compile C (not C++) code? Last I heard it was years behind on the C standards. In any case few in the Linux community care too much about closed source compilers. Lack of MSVC support for cleanups would be amongst the least of your problems if you were trying to use it to compile systemd or libvirt.
Does MSVC support C99 yet? Much less C11 or a future C2x?
Their priority has always been C++ and for some time their stance on C was basically "I guess we're willing to compile the subset of C that is valid C++."
Where's the effort? I happen to follow WG14 proposals closely, but I haven't seen this particular one discussed prominently or incorporated into the latest working draft. I don't even remember a single paper with this feature submitted to WG14 for consideration.
54 comments
[ 0.20 ms ] story [ 119 ms ] threadhttps://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...
Example use: https://github.com/FRRouting/frr/blob/master/lib/frr_pthread...
(FRR requires a GCC/clang compatible compiler, so no fallback/workaround.)
Try it. I just did, and it works.
Defining macros with C keyword names is not such a hot idea.
edit Looking at the implementation, it seems to use setjmp/longjmp on some platforms. Nasty. I believe Boost.ScopeExit does some macro trickery to leverage C++'s RAII, which isn't so bad.
[0] https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/ht...
https://www.boost.org/doc/libs/1_72_0/libs/scope_exit/doc/ht...
You'd have to define an empty class with a destructor, then instantiate the class at the appropriate place in your code. The point of Boost.ScopeExit is to handle that boilerplate for you.
> I'm not sure if I see the advantage over widely understood first-class language features
The advantage is simply reduced boilerplate. You're right that it's 'less standard' and more likely to baffle the reader, if they're not already familiar with ScopeExit.
> consider using std::unique_ptr with a custom deleter -- this construct is for managing resources in a general sense, not necessarily just memory allocation
This strikes me as hijacking a memory-management facility, using it for a different purpose than its intent. That's bad for readability. I'd prefer either ScopeExit or the dummy object pattern I described.
I would argue that every time you want to execute something on scope exit, that essentially models a resource or lock of some kind. Which means the constructor wouldn't be empty (it would acquire the resource), and writing the class wouldn't really be "boilerplate", it would be clean design.
>This strikes me as hijacking a memory-management facility, using it for a different purpose than its intent. That's bad for readability. I'd prefer either ScopeExit or the dummy object pattern I described.
I think it is much better for readability, because it usually models the thing you want to do much more precisely. Although it is a shame that only unique_ptr is standard and not unique_handle, but you could write that yourself relatively quickly. This case doesn't just model that you want to execute something at the end of a scope, but that you have a handle to a resource that you can std::move around and that will get destroyed at the appropriate time (if you model your data correctly).
I agree that proper RAII is generally the way to go. I think of ScopeExit as being for those situations where you have to deal with C-style code. One way is to wrap the C-style code in C++ and leverage C++'s RAII. The alternative is to write C-style code yourself, but you can still use ScopeExit to ensure you didn't miss any edge-cases where cleanup is necessary (multiple points of return, exceptions, etc).
In this way, ScopeExit is for 'C++ as a better C' programming.
> This case doesn't just model that you want to execute something at the end of a scope, but that you have a handle to a resource that you can std::move around and that will get destroyed at the appropriate time
This is going far further down the C++ rabbit-hole than what we started with, though. If I just want to be sure that I didn't miss any places where I need to call free (or similar) in my C-style code, ScopeExit is just the thing.
(Callbacks from the garbage collector on deallocation are even worse. That way lies locking problems and "re-animation", a nightmare in Managed C++. One of the cleaner solutions to scope-based cleanup is the "with" clause in Python, which is based on "with" constructs in Common LISP. Only thing I've seen where exceptions in a destructor work right.)
> It's not a technical problem, but a social problem. Yes, I would definitely prefer the c++ RAII (and refcounts would be nice too). If you say 'my project is in c++', that sends a certain message to prospective contributors, about what your priorities and ideals are. It can attract certain kinds of contributors and discourage others. Then you have the problem of how to define your subset of c++. It's easy to say 'no exceptions, no RTTI, no STL'. But there are subtler things. As you mention, templates are occasionally useful. But sometimes they're completely superfluous. Do you allow virtual functions? Multiple inheritance? The answer is almost invariably 'maybe'; you have to exercise taste. I can do that by myself, for my own project. But if I want to be able to accept contributions from others, I need a clearer set of contribution guidelines than 'wherever my whimsy takes me', and for such a purpose 'whatever the c compiler accepts' is the best I can do.
> Also, tcc is about 10x faster than gcc and clang, which makes development a joy.
1: https://www.reddit.com/r/programming/comments/f4gb6n/i_made_...
how about you just don't do that like 99% of modern C++ projects and just accept "whatever the C++ compiler accepts".
Remember that "whatever the C compiler accepts" covers most of IOCCC too.
- Chrome: https://chromium-cpp.appspot.com/ - Mozilla: https://developer.mozilla.org/en-US/docs/Mozilla/Using_CXX_i...
This is really not my experience. Any decent-sized program will have parts more in a functional style, others in a more OOP one, others in regular-typed Stepanov bliss, others in template or constexpr metaprograms.... and this causes zero issues in practice once people get past their assumptions about what clean code should look like.
(+) offer not valid on microcontrollers or proprietary C++ compilers
C with improvements suffers from uncanny valley. If it's not plain C, then it's weird and non-standard, and C programmers don't want it.
So if you're deviating from pure boring C, then you can just as well switch to another language, and one that doesn't have legacy baggage.
Do people even use MSVC to compile C (not C++) code? Last I heard it was years behind on the C standards. In any case few in the Linux community care too much about closed source compilers. Lack of MSVC support for cleanups would be amongst the least of your problems if you were trying to use it to compile systemd or libvirt.
Their priority has always been C++ and for some time their stance on C was basically "I guess we're willing to compile the subset of C that is valid C++."
[0] https://chiselapp.com/user/rkeene/repository/tcl-defer/