17

I have a ScopedLock class which can help to release lock automatically when running out of scope. However, the problem is: Sometimes team members write invalid lock-code such as

{
    ScopedLock(mutex);   // anonymous
    xxx;
}

The above code is wrong because the ScopedLock object is constructed and destructed immediately, so it fails to lock the expected area (xxx). I want the compiler to give an error when trying to compile such code. Can this be done?

I have searched g++ warning options, but fail to find the right one.

leemes
  • 44,967
  • 21
  • 135
  • 183
Raymond
  • 561
  • 1
  • 4
  • 14
  • 1
    I don't think you can forbid this (or even generate a compiler diagnostic). A much more efficient (and satisfying, maybe) solution would be to slap your co-workers when they do it, until they finally stop doing it. ;) – syam Apr 24 '13 at 10:37
  • 3
    BTW, the actual name is *temporary objects* not *anonymous objects*. – syam Apr 24 '13 at 10:42
  • Unfortunately, it looks like http://lists.cs.uiuc.edu/pipermail/cfe-dev/2010-December/012755.html never made it into Clang. – Sebastian Redl Apr 24 '13 at 13:23
  • 1
    Of course, there's the valid use `ScopedLock(mutex), foo();`. In isolation it's never necessary, but it can be quite handy in more complex expressions: `Bar(2, (ScopedLock(mutex), foo()), 3)`. – MSalters Apr 25 '13 at 08:40
  • [P0577](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0577r1.pdf) proposes to avoid this by repurposing the `register` keyword to mean 'associate the following anonymous object(s) with this entire scope'. – underscore_d Jan 01 '19 at 11:55

7 Answers7

9

I have seen an interesting trick in one codebase, but it only works if your scoped_lock type is not a template (std::scoped_lock is).

#define scoped_lock(x) static_assert(false, "you forgot the variable name")

If you use the class correctly, you have

scoped_lock lock(mutex);

and since the scoped_lock identifier isn't followed by an open paren, the macro won't trigger and the code will remain as it is. If you write\

scoped_lock(mutex);

the macro will trigger and the code will be substituted with

static_assert(false, "you forgot the variable name");

This will generate an informative message.

If you use a qualified name

threads::scoped_lock(mutext);

then the result will still not compile, but the message won't be as nice.

Of course, if your lock is a template, the bad code is

scoped_lock<mutex_type>(mutex);

which won't trigger the macro.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • I didn't know that it's even possible to write the macro name without parentheses and it's still valid (and not expanded). This is a nice one. +1 – leemes Apr 24 '13 at 12:32
  • Other pitfalls I can think of: Typedefs will fail unless a new macro is also introduced along with it. Also, when you inherit from a class which has such a macro, you have to use a typedef first (if you don't, AFAIK you can't invoke the super constructor with the mutex as an argument) as well as for the sub class the trick fails again. In both cases (typedef + sub class), you can of course write another macro and are safe again, but as always: you have to remember it in the first place. – leemes Apr 24 '13 at 12:35
  • I don't know about `std::scoped_lock` was this back in the days of this question? – Walter Nov 30 '16 at 19:22
  • @Walter Might have been a mistake on my part, confusing it with Boost.Thread's original name. std has unique_lock and lock_guard. – Sebastian Redl Dec 01 '16 at 11:02
  • as a coworker pointer out, this won't trigger either `scoped_loc{mutex};` – rrosa Sep 03 '19 at 02:20
5

No, unfortunately there is no way to do this, as I explored in a blog post last year.

In it, I concluded:

I guess the moral of the story is to remember this story when using scoped_locks.


You can try to force all programmers in your team to use a macro, or a range-for trick, but then if you could guarantee that in every case then you'd be able to guarantee catching this bug in every case also.

You are looking for a way to programmatically catch this specific mistake when it's made, and there is none.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
4

You can use a class and deleted function with the same name. Unfortunately this requires adding "class" keyword before the type.

class Guard
{
public:
  explicit Guard(void)
  {
  }
};

static void Guard(void) = delete;

int main()
{
  // Guard(); // Won't compile
  // Guard g; // Won't compile
  class Guard g;
}
Kuba S.
  • 186
  • 2
  • 4
2

To avoid this, introduce a macro which does this for you, always using the same name for the locker:

#define LOCK(mutex) ScopedLock _lock(mutex)

Then use it like this:

{
    LOCK(mutex);
    xxx;
}

As an alternative, Java's synchronize block can be simulated using a macro construct: In a for-loop running always exactly once, I instantiate such a locker in the initialization statement of the for-loop, so it gets destroyed when leaving the for-loop.

However, it has some pitfalls, unexpected behavior of a break statement being one example. This "hack" is introduced here.


Of course, none of the above methods fully avoid accidental code like your example. But if you're used to write locking mutexes using one of the two macros, it will less likely happen. As the name of the locker class will then never appear in the code except in the macro definition, you can even introduce a commit hook in a version control system to avoid committing invalid code.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • Actually, this abuse of for loops can be done without those pitfalls with the new range-based loop (http://stackoverflow.com/a/9657748/46642) – R. Martinho Fernandes Apr 24 '13 at 10:46
  • Okay, but then you have to remember to use `LOCK` instead of `ScopedLock`. I don't think that you've really solved anything. – Lightness Races in Orbit Apr 24 '13 at 10:46
  • +1, interesting `synchronize` trick. But because of the pitfalls and the new non-standard semantics it introduces I wouldn't recommend its usage. As for the `LOCK` macro, as usual when generating local variables behind the scenes it should create an identifier name based on file name and line to reduce possible name clashes (can't remember the preprocessor syntax for that right now). – syam Apr 24 '13 at 10:47
  • @syam Regarding the names introduced by macros: In this case, even nested `synchronize` blocks won't be a problem: They shadow each other, but since nobody uses the variable, the only problem is when it conflicts with your other local variables AND you want to access such a variable within the block AND it was defined outside, which is not very likely if you choose `_some_stRang3_n4me` for it. – leemes Apr 24 '13 at 10:49
  • Only the final paragraph saved you a -1 ;) – Lightness Races in Orbit Apr 24 '13 at 10:51
  • @leemes: What I meant was `LOCK(mutex1); /*...*/ LOCK(mutex2);` in the same scope, as it sometimes happens. ;) – syam Apr 24 '13 at 10:53
  • @LightnessRacesinOrbit Phew! I know macros in general can be evil, but when taking a lot care, they *can* be helpful. Forbidding not to use them might be one requirement. ;) – leemes Apr 24 '13 at 10:53
  • @syam Ok, just thought about the same thing: Then we can just append the mutex name and hope it's always a symbol and not an arbitrary expression ;) So finally the file name + number trick might be the solution. – leemes Apr 24 '13 at 10:53
  • @leemes you can also make up a name using the `__LINE__` macro, or the superior but non-standnard `__COUNTER__`. – R. Martinho Fernandes Apr 24 '13 at 10:54
  • 1
    @leemes: I didn't mean because of the macro. I meant because up until the last paragraph you did not address the question! – Lightness Races in Orbit Apr 24 '13 at 10:55
  • 1
    Thank you very much. I think I can define #define ScopeLock(x) wrong_usage_lock, so when team member write ScopedLock(x) the compiler will throw error. – Raymond Apr 24 '13 at 11:44
  • @Raymond Oh dear, I guess everyone has overlooked that one... Of course! Go ahead. However: Some IDEs might have problems with that, as they might interpret `ScopeLock` as an invocation of the macro. Maybe you want to put this as an own answer? It's possible on StackOverflow and also a good way to receive feedback for this solution. Maybe there are some pitfalls? I don't know... – leemes Apr 24 '13 at 12:28
1

AFAIK there's no such a flag in gcc. A static analyzer may better suit your needs.

Nico Brailovsky
  • 318
  • 2
  • 7
1

In C++17, a type can be marked [[nodiscard]], in which case a warning is encouraged for an expression that discards a value of that type (including by the case described here that resembles a declaration of a variable). In C++20, it can be applied to individual constructors as well if only some of them cause this sort of problem.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
0

replace it with macro

#define CON2(x,y) x##y
#define CON(x,y) CON2(x,y)
#define LOCK(x)  ScopedLock CON(unique_,__COUNTER__)(mutex)

usage

{
  LOCK(mutex);
  //do stuff
}

This macro will generate unique names for locks, allowing lockeng of other mutexes in inner scopes

kassak
  • 3,974
  • 1
  • 25
  • 36
  • This is no more useful than "add a variable name". It doesn't present a way to catch the bug. – Lightness Races in Orbit Apr 24 '13 at 10:48
  • @LightnessRacesinOrbit If they will use macro instead, they won't be missing variable name =) – kassak Apr 24 '13 at 10:49
  • 1
    And if they didn't miss the variable name, they won't be missing the variable name. The point is that _you have to remember to do something_ in both cases. The question is about programmatically detecting the bug. – Lightness Races in Orbit Apr 24 '13 at 10:50
  • @LightnessRacesinOrbit arguable. What should they remember in case of that macro? The question is not about detection, but about avoiding such bugs. – kassak Apr 24 '13 at 10:55
  • 1
    They must remember to use the macro in the first place. – Lightness Races in Orbit Apr 24 '13 at 10:57
  • @LightnessRacesinOrbit that sounds like, using smart pointers is not an answer, because programmer should remember to use them instead of raw ones. Missing unused variable name is problem, using another construct to avoid this is not. – kassak Apr 24 '13 at 11:01
  • 1
    Using smart pointers is not the answer to "how do I make my compiler catch all incorrect uses of raw pointers?" That doesn't mean it's not a good idea, of course ;) – Lightness Races in Orbit Apr 24 '13 at 11:03
  • @LightnessRacesinOrbit stop trolling me :) – kassak Apr 24 '13 at 11:09
  • 1
    @kassak Lightness Races in Orbit is right about that. I did exactly the same mistake first in my answer when introducing a macro. Then I realized: "Hey, but now the programmer still *has* to use the macro while he is *still* allowed to write code like in the question. Now how can I avoid it?" => Simply by forbidding to use the locker without the macro, which is only possible if such a macro exists. So it is an additional requirement to address the question. Only introducing the macro is not the answer. Lightness Races in Orbit is not trolling in my opinion but rather explaining the difference. – leemes Apr 24 '13 at 11:31
  • Okay-okay, I believe you guys. But in my opinion, this is like comparing errors: "not using smart-ptrs, where needed" and "semicolon after if". One is just mistype, other is just a bad idea – kassak Apr 24 '13 at 12:14