Disallowing creation of the temporary objects

后端 未结 8 896
南方客
南方客 2020-11-29 09:01

While debugging crash in a multithreaded application I finally located the problem in this statement:

CSingleLock(&m_criticalSection, TRUE);
相关标签:
8条回答
  • 2020-11-29 09:34

    I see that in 5 years nobody has come up with the most simple solution:

    #define LOCK(x) CSingleLock lock(&x, TRUE);
    ...
    void f() {
       LOCK(m_criticalSection);
    

    And now only use this macro for creating locks. No chance to create temporaries any more! This has the added benefit that the macro can be easily augmented to perform any kind of checking in debug builds, for example detecting inappropriate recursive locking, recording file and line of the lock, and much more.

    0 讨论(0)
  • 2020-11-29 09:35

    Old question, but I have a couple of points to add.

    By define a macro function with the same name as the class, you can trigger a static assertion with a helpful message when someone forgets the variable name. live here

    class CSingleLock {
     public:
      CSingleLock (std::mutex*, bool) { }
    };
    
    // must come after class definition
    #define CSingleLock(...) static_assert(false, \
        "Temporary CSingleLock objects are forbidden, did you forget a variable name?")
    

    The macro won't match when there is a variable name. However, this doesn't help in the case of uniform initialization; you can't catch CSingleLock{&m, true}. PfhorSlayer's answer works with uniform initialization so it is safer to use, at the cost of a more confusing error message. I would still reccomend that solution over mine. Unfortunately all these macro solutions fail when the type is in a namespace.


    Another solution is to produce a compiler warning using [[nodiscard]]

    class CSingleLock {
     public:
      [[nodiscard]] CSingleLock (std::mutex*, bool) { }
    };
    

    If you create a temporary clang will warn you saying:

    warning: ignoring temporary created by a constructor declared with 'nodiscard' attribute [-Wunused-value]
      CSingleLock(&m, true);
      ^~~~~~~~~~~~~~~~~~~~~
    

    GCC 9.2 seems to have a problem with [[nodiscard]] on constructors though. It still gives additional warnings if you don't use the result. The problem is fixed on head which at the time of writing this is gcc-10 20191217 on wandbox.

    0 讨论(0)
  • 2020-11-29 09:39

    First, Earwicker makes some good points -- you can't prevent every accidental misuse of this construct.

    But for your specific case, this can in fact be avoided. That's because C++ does make one (strange) distinction regarding temporary objects: Free functions cannot take non-const references to temporary objects. So, in order to avoid locks that blip into and out of existence, just move the locking code out of the CSingleLock constructor and into a free function (which you can make a friend to avoid exposing internals as methods):

    class CSingleLock {
        friend void Lock(CSingleLock& lock) {
            // Perform the actual locking here.
        }
    };
    

    Unlocking is still performed in the destructor.

    To use:

    CSingleLock myLock(&m_criticalSection, TRUE);
    Lock(myLock);
    

    Yes, it's slightly more unwieldy to write. But now, the compiler will complain if you try:

    Lock(CSingleLock(&m_criticalSection, TRUE));   // Error! Caught at compile time.
    

    Because the non-const ref parameter of Lock() cannot bind to a temporary.

    Perhaps surprisingly, class methods can operate on temporaries -- that's why Lock() needs to be a free function. If you drop the friend specifier and the function parameter in the top snippet to make Lock() a method, then the compiler will happily allow you to write:

    CSingleLock(&m_criticalSection, TRUE).Lock();  // Yikes!
    

    MS COMPILER NOTE: MSVC++ versions up to Visual Studio .NET 2003 incorrectly allowed functions to bind to non-const references in versions prior to VC++ 2005. This behaviour has been fixed in VC++ 2005 and above.

    0 讨论(0)
  • 2020-11-29 09:47

    No, there is no way of doing this. Doing so would break almost all C++ code which relies heavily on creating nameless temporaries. Your only solution for specific classes is to make their constructors private and then always construct them via some sort of factory. But I think the cure is worse than the disease!

    0 讨论(0)
  • 2020-11-29 09:48

    Compiler shouldn't disallow temporary object creation, IMHO.

    Specially cases like shrinking a vector you really need temporary object to be created.

    std::vector<T>(v).swap(v);
    

    Though it is bit difficult but still code review and unit testing should catch these issues.

    Otherwise, here is one poor man's solution:

    CSingleLock aLock(&m_criticalSection); //Don't use the second parameter whose default is FALSE
    
    aLock.Lock();  //an explicit lock should take care of your problem
    
    0 讨论(0)
  • 2020-11-29 09:51

    I don't think so.

    While it's not a sensible thing to do - as you've found out with your bug - there's nothing "illegal" about the statement. The compiler has no way of knowing whether the return value from the method is "vital" or not.

    0 讨论(0)
提交回复
热议问题