Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing users from creating unnamed instances of a class [duplicate]

Tags:

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:

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

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

like image 793
Vittorio Romeo Avatar asked Nov 30 '16 09:11

Vittorio Romeo


2 Answers

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  
like image 200
David Haim Avatar answered Oct 18 '22 22:10

David Haim


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

like image 40
kubanrob Avatar answered Oct 18 '22 22:10

kubanrob