For many RAII "guard" classes, being instantiated as anonymous variables does not make sense at all:
{ std::lock_guard<std::mutex>{some_mutex}; // Does not protect the scope! // The unnamed instance is immediately destroyed. }
{ scope_guard{[]{ cleanup(); }}; // `cleanup()` is executed immediately! // The unnamed instance is immediately destroyed. }
From this article:
Anonymous variables in C++ have “expression scope”, meaning they are destroyed at the end of the expression in which they are created.
Is there any way to prevent the user from instantiating them without a name? ("Prevent" may be too strong - "making it very difficult" is also acceptable).
I can think of two possible workarounds, but they introduce syntactical overhead in the use of the class:
Hide the class in a detail
namespace and provide a macro.
namespace detail { class my_guard { /* ... */ }; }; #define SOME_LIB_MY_GUARD(...) \ detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
This works, but is hackish.
Only allow the user to use the guard through an higher-order function.
template <typename TArgTuple, typename TF> decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f) { make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs)); f(); }
Usage:
with_guard(std::forward_as_tuple(some_mutex), [&] { // ... });
This workaround does not work when the initialization of the guard class has "fluent" syntax:
{ auto _ = guard_creator() .some_setting(1) .some_setting(2) .create(); }
Is there any better alternative? I have access to C++17 features.
The only sensible way I think about is to make the user pass the result of guard_creator::create
to some guard_activator
which takes a lvalue-reference as a parameter.
this way, the user of the class has no way but either create the object with a name (the sane option that most developers will do), or new
it then dereference (insane options)
for example, you said in the comments you work on a non allocating asynchronous chain creator. I can think on an API which looks like this:
auto token = monad_creator().then([]{...}).then([]{...}).then([]{...}).create(); launch_async_monad(token); //gets token as Token&, the user has no way BUT create this object with a name
If have access to the full potential of C++17, you can expand the idea of using a static factory function into something usefull: guarantied copy elision makes the static factory function possible even for non-movable classes, and the [[nodiscard]] attributes prompts the compiler to issue a warning if the return value is ignored.
class [[nodiscard]] Guard { public: Guard(Guard& other) = delete; ~Guard() { /* do sth. with _ptr */ } static Guard create(void* ptr) { return Guard(ptr); } private: Guard(void* ptr) : _ptr(ptr) {} void* _ptr; }; int main(int, char**) { Guard::create(nullptr); //auto g = Guard::create(nullptr); }
Compile in Compiler Explorer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With