Here is a gist of how the scope_guard machinary could be written:
Yes, looks like a good first approximation. Looking at specification from the TS (aka P0052) – and off the top of my head maybe there maybe needs to be:
- constexpr on various functions (not in TS, but think should be now?)
- remove iostream include
- header should be scope - no
_guard(I wonder if in beman it should be without .hpp like standard?) - constructors are explicit in places in TS - aka:
template<class EFP> explicit scope_guard (EFP&& f)
- there’s some conditional noexcept logic on
scope_success(alsoscope_guard– more below)
scope_success() noexcept(noexcept(exit_function()));
- probably should explicitly disable assignment like in spec – should be anyway I think, but can’t hurt
scope_guard (const scope_guard &)=delete;
scope_guard & operator=(const scope_guard &)=delete;
scope_guard & operator=(scope_guard &&)=delete;
scope_guardshould be in a detail namespace - it’s not part of the api (more below)
Note that a pro-trick when I’m evaluating wording, I copy the synopsis from the paper and work with it until it compiles or doesn’t – so many bugs in wording discovered this way. My point being that what we’re doing here is a backward check of the specification by attempting to implement it.
So my 15 minute check of your implementation against the spec shines the light on at least a couple specification problems from P0052. Specifically
~scope_guard () noexcept(see below )
There’s no text corresponding to see below – in fact there’s almost no specification of scope_guard itself.
The intent in the wording is that scope_guard is exposition-only – that is not available to end users (hence my detail namespace comment above). But you don’t know that because it’s not marked properly as exposition only. Also in the wording it should be called scope-guard (uncompilable kebob case) - it’s just in italics…
Anyway, I think you should turn this draft into an initial PR on top of @river 's.
Thank you for the information.
My implementation was just to get the example working… there are still many things wrong with it, to make it work in all cases.
I think the correct mindset would be: we are reverse engineering the paper.
I will get on it.
I’ve started adding issues to the repo to document stuff we’re finding here so it can go into the paper. I’ll be traveling to Europe later today for the Hagenberg c++ meeting so I might be able to get a draft outline for the paper in place. I don’t know if Peter will be there, but I’ll be reaching out to him and the Boost author soon to see if they want to participate.
An alternative to the synopsis that I provided earlier is now in: GitHub - bemanproject/scope at scope-guard-demo
Available in: include/beman/scope/scope_guard_demo.hpp
Also the example in this branch has been modified to demonstrate how this demo can be used.
The synopsis has already been copied into the main branch, I’m working on making the intial implementaion. The demo code can be used as basis, but it requires a change - from using constructor parameters to using template parameters.
Please review the infrastructure pulls.
- exception header
Since scope is fundamentally about dealing with exceptions I think that is a requirement.
I might be overthinking this.
The way I see it, a scope_guard does not deal with exceptions by itself.
Reacting to exceptions is a customization of a scope_guard.
Similarly, making a scope_guard cancelable or releasable is another customization.
These customizations can be applied individually or together, or even with other custom conditions.
As I see it, a scope_guard consists of:
- an initialization step: creating the scope_guard object,
- a checking step: verifying if conditions are met to execute the finalization function,
- a finalization step: executing previously deferred functions if the above check is fulfilled.
scope_fail and scope_success are just convenience classes, which are essentially preconfigured scope_guards.
So in the end scope_guard isn’t part of the public interface because it’s marked exposition only in the TS. Hence the only relevant question is do the 3 public interface classes need exceptions – they do and hence my point. What it means is that implementations do not need to create a scope_guard at all – they can just code the 3 types. So users are NOT free to derive to make their own, etc. As I think I put in the design issue I this should be reconsidered I think.
I guess even if scope_guard wasn’t this way it’s in the same header as the others so exception is still needed. And in 3 years the world will start shifting to import std; and it’ll all be water under the bridge.
I’ve archived the original repo - @Robert-Andrzejuk you seem to have a fork of it, but there’s little activity. I expect to simply delete it soon.
The repo has been deleted.
what is the use case for std::experimental::unique_resource:
const D& get_deleter() const noexcept;
// Returns: deleter.
Since I was invited to “scope” just a quick remark from my mobile: I abandoned scope guards and especially unique resource, because I think using those is poor practice, where a better resource management abstraction is missing. I therefore intensified my teaching of how to implement Scoped and Unique Manager types instead. I think it would be a mistake to standardize something that leads to sloppy design.
Regards
Peter
Thanks for chiming in Peter and welcome! Can you elaborate on why you find using scope guards and unique resource as poor practice?
You may found also some critical notes at boost:
Hi David,
deeper thought and teaching C++ design made me come tho my conclusion. I teach C++ classes/types coming in different flavors/kinds: Values, Relation(pointer-like), OO-Polymorphic (virtual), and Managers (defining a destructor with a body). Without going into more detail, Manager types manage a single resources come in three subflavors, depending on movabiltiy/copyability:
- Scoped Managers - non-movable (Rule of DesDeMovA)
- Unique Managers - move-only (i.e. optional as member)
- General Managers - copyable - value types, move as optimization
Since it is very simple and teachable to create a Scoped/Unique Manager type and also much better testable than using a scope guard/unique_resource I consider the generic scope guards/unique_resource a hack that hinders testabiltiy and reuse. Having a domain specific manager type is much better design.
Rule of DesDeMovA: define move-assignment as =delete to prevent all copy-move operations with the least amount of code.
My class design recommendations are also reflected in the MISRA-C++:2023 guidelines and in the draft of WG23’s C++ vulnerabilities document.
If you need more details, you can find some talk videos by me from a couple of years, or you can look at the publicly visible teaching material of my C++ trainings (copyrighted) or ask me via email with specific questions.
Regards
Peter
Thanks Peter. If you prefer a domain-specific manager type, wouldn’t it still be more convenient if there were a general facility to build upon?
class DomainSpecificManager : public std::generic_manager<…> {
DomainSpecificManager(Resource r)
: std::generic_manager<…>( [=](){free_resource(r);} )
{}
};
Hi David,
sorry for my latency in replying to your question.
I doubt that it is a benefit wrt teachability and usabilty to provide a
generic manager class template, since resource allocation should be part
of the manager class constructor and thus a “generic manager” base
wouldn’t help much wrt resource representation and allocation. Also
error handling of the underlying allocation/deallocation might be a bit
more involved (you don’t want your destructor to throw.)
For example, I just made up a simple ScopedThread manager example that
is about 12 lines at
a UniqueThread that I just made up while writing this email is about 20
lines
So I think, teaching C++ programmers how to properly write manager
classes is much more beneficial than providing a generic, hard to get
right infrastructure where using it might introduce subtle resource
leaks, because allocation is not part of a constructor.
Regards
Peter.
ChatGPT suggest small improvements?
#include <thread>
#include <utility>
class UniqueThread {
public:
UniqueThread() noexcept = default;
template<typename F, typename... Args>
explicit UniqueThread(F&& f, Args&&... args)
: t(std::forward<F>(f), std::forward<Args>(args)...)
{}
~UniqueThread() noexcept {
join_if_joinable();
}
UniqueThread(UniqueThread&& other) noexcept
: t(std::move(other.t))
{}
UniqueThread& operator=(UniqueThread&& other) noexcept {
if (this != &other)
{
join_if_joinable();
t = std::move(other.t);
}
return *this;
}
UniqueThread(const UniqueThread&) = delete;
UniqueThread& operator=(const UniqueThread&) = delete;
bool joinable() const noexcept {
return t.joinable();
}
void join() {
t.join();
}
void swap(UniqueThread& other) noexcept {
t.swap(other.t);
}
private:
void join_if_joinable() noexcept {
if (t.joinable())
t.join();
}
std::thread t;
};
in my hurry i forgot the self-assignment check, i should have copied from my slides
however, implementing swap and defining the deleted functions are completely superfluous imho and should not be taught (swap might be an optimization depending on std::thread’s representation but move operations are sufficient for std::swap to work properly)
Regards
peter
Peter Sommerladpeter.cpp@sommerlad.ch
+41-79-432 23 32
Also the omission of the additional functionality suggested was intentional.
If you care about explicitly joining stay with std::thread.
I have chosen that just as an example to document how easy it is to create scoped and unique manager types, and if I had followed my teaching slides I wouldn’t have made the mistake of forgetting to handle self-assignment.
Putting in too much unneeded functionality is what makes standard library types so intricate.