Emulate C# lock statement in C++

霸气de小男生 提交于 2019-12-11 03:10:03

问题


Intro: For synchronization, C# offers the System.Threading.Monitorclass, offering thread synchronization routines such as Enter(), Exit(), TryEnter() and alike.

Furthermore, there is the lock statement that makes sure a lock gets destroyed when a critical code block is left, either by normal execution flow or by an exception:

private static readonly obj = new Object();

lock(obj) {
   ...
}

Problem: In C++, for this purpose, we got the RAII wrappers std::lock_guard and std::unique_lock that are not applied to Monitor classes but to types fulfilling the Lockable concept. However, I consider this approach syntactically weaker than the way C# implemented it for several reasons:

You pollute the local scope with a variable name that cannot be reused. This can be countered by adding new scopes like

  {
      std::unique_lock<std::mutex> lck{ mtx };
      ...
  }

But I find this notation rather awkward-looking. What troubles me even more that this is valid C++:

std::unique_lock<std::mutex>{ mtx ]; // note there is no name to the lock!
...

So by forgetting to give a proper name to the lock guard, this statement will be interpreted as a variable declaration named "mtx" of type std::unique_lock<std::mutex>, without having anything locked!

I want to implement something like the lock statement from C# in C++. In C++17, this can be accomplished very easily:

#define LOCK(mutex) if(std::lock_guard<decltype(mutex)> My_Lock_{ mutex }; true)

std::mutex mtx;
LOCK(mtx) {
   ...
}

Q: How can I implement this in C++11/14?


回答1:


Putting aside the "should you do this", here's how:

While it's not quite the same, since it requires a semi-colon, it's near enough that I feel I may present it. This pure C++14 solution basically just defines the macro to start a lambda which is immediately executed:

template<typename MTX>
struct my_lock_holder {
    MTX& mtx;
    my_lock_holder(MTX& m) : mtx{m} {}
};

template<typename MTX, typename F>
void operator+(my_lock_holder<MTX>&& h, F&& f) {
    std::lock_guard<MTX> guard{h.mtx};
    std::forward<F>(f)();
}

#define LOCK(mtx) my_lock_holder<decltype(mtx)>{mtx} + [&]

The my_lock_holder just nabs the mutex reference for later, and allows us to overload operator+. The idea is that the operator creates the guard and execute the lambda. As you can see the macro defines a default reference capture, so that lambda will be able to reference anything in the enclosing scope. Then it's pretty much straight forward:

std::mutex mtx;
LOCK(mtx) {

}; // Note the semi-colon

And you can see it build live.




回答2:


Inspired by StoryTeller's great idea, I think I found a viable solution myself, despite being somewhat a "hack":

template <typename T>
struct Weird_lock final : private std::lock_guard<T> {
    bool flip;
    Weird_lock(T& m) : std::lock_guard<T>{ m }, flip{ true } { }

    operator bool() noexcept {
        bool old = flip;
        flip = false;
        return old;
    }
};

#define LOCK(mutex) for(Weird_lock<decltype(mutex)> W__l__{ mutex }; W__l__;)

The good thing is that it doesn't need a semicolon in the end. The bad is the need for an additional bool, but from what I see in godbolt.org, the compiler optimizes this out anyways.




回答3:


I suggest you do:

#define UNIQUE_NAME(name) name##__COUNTER__
#define LOCK(mutex) std::lock_guard<decltype(mutex)> UNIQUE_NAME(My_Lock){ mutex };

Using the COUNTER preprocessor symbol will generate a unique variable name that you simply don't care about.



来源:https://stackoverflow.com/questions/47602539/emulate-c-sharp-lock-statement-in-c

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