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

£可爱£侵袭症+ 提交于 2019-11-30 00:42:17

问题


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.


回答1:


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 



回答2:


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




回答3:


You could use an extensible lint tool such as Vera++ https://bitbucket.org/verateam/vera/wiki/Home it lets you lint your code, you can create new rules using Python or tcl (I prefer Python)

A possible flow would be - after each commit, your CI system (e.g Jenkins) will run a job that executes Vera++ and validate such oversights, upon a failure a mail would be issued to the committer.




回答4:


The canonical way to prevent a class from being instantiated is by making its constructor private. To actually get one of the desired instances, you call a static method, which returns a reference to a constructed object.

class Me {
public:
    static Me &MakeMe() { return Me(); }
private:
    Me();
}; // Me

This doesn't help of course - but it'd probably make the programmer pause!

int main() {
    Me(); // Invalid
    Me m; // Invalid
    Me::MakeMe(); // Valid - but who'd write that?
    Me m = Me::MakeMe();
} // main()

I know this isn't a direct analog to the Guard instances that you describe - but maybe you could adapt the concept?



来源:https://stackoverflow.com/questions/40884335/preventing-users-from-creating-unnamed-instances-of-a-class

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!