10

I have a function foo() protected by a mutex m which is defined as a local static variable of foo(). I'd like to know whether it's safe to call foo() in the destructor of an object bar with static storage duration:

// foo.h
void foo();

// foo.cpp
#include "foo.h"
#include <mutex>
void foo()  {
    static std::mutex m;
    std::lock_guard<std::mutex> lock(m);
    // ...
}

// bar.h
struct Bar { ~Bar(); };
extern Bar bar;

// bar.cpp
#include "bar.h"
#include "foo.h"
Bar::~Bar() { foo(); }
Bar bar;

// main.cpp
int main() {
    Bar bar;
    return 0;
}

If std::mutex is trivially destructible, this should be safe, because bar will be destructed before m. On GCC 5.4, Ubuntu 16.04, calling std::is_trivially_destructible<std::mutex>::value returns true, so it seems okay at least in this compiler. Any definitive answer?

Related: Google C++ Style Guide on Static and Global Variables


Edit

Apparently, I wasn't clear enough and should have provided more context. Yes, the underlying problem is that I want bar to be destructed before m. This is the "destruction" part of the well known "static initialization fiasco problem", see for example:

https://isocpp.org/wiki/faq/ctors#construct-on-first-use-v2

The point is simple: if there are any other static objects whose destructors might use ans after ans is destructed, bang, you’re dead. If the constructors of a, b and c use ans, you should normally be okay since the runtime system will, during static deinitialization, destruct ans after the last of those three objects is destructed. However if a and/or b and/or c fail to use ans in their constructors and/or if any code anywhere gets the address of ans and hands it to some other static object, all bets are off and you have to be very, very careful.

This is why Google recommends against using static objects, unless they are trivially destructible. The thing is, if the object is trivially destructible, then the order of destruction doesn't really matter. Even if m is "destructed" before bar, you can still in practice use m in the destructor of bar without crashing the program, because the destructor effectively does nothing (it doesn't deallocate any memory or release any other type of resources).

And in fact, if m is trivially destructible, then the program may not even destruct m at all, which actually ensures that m is "destructed" after bar or any other static objets which are not trivially destructible. See for instance:

http://en.cppreference.com/w/cpp/language/lifetime#Storage_reuse

A program is not required to call the destructor of an object to end its lifetime if the object is trivially-destructible or if the program does not rely on the side effects of the destructor.

For these reasons, it is in practice overkill to use complex singleton idioms such as the Nifty Counter idiom if your singleton is trivially destructible.

In other words, if std::mutex is trivially destructible, then my code sample above is safe: m is either destructed after bar, or it is "technically destructed" before bar but wouldn't cause a crash anyway. However, if std::mutex is not trivially destructible, then I may need to use the Nifty Counter idiom instead, or alternatively the simpler but "purposefully leaking" Trusty Leaking idiom.

Related:

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
  • 2
    This quote is of concern _"Storage occupied by trivially destructible objects may be reused without calling the destructor."_ from: http://en.cppreference.com/w/cpp/types/is_destructible Guess it depends on if the implementation wants/needs to release the `native_handle` – Richard Critten Mar 17 '18 at 22:22
  • 2
    [Related](https://stackoverflow.com/questions/36160343). Seems to imply "no". – Drew Dormann Mar 17 '18 at 22:23
  • 2
    The standard says it `is_destructable` rather than `is_trivially_destructable`. Seems more relaxed. – Galik Mar 17 '18 at 22:45
  • 1
    I can't get your point. How does trivially destructible impact destruction order ? Static duration destruction order is in reverse of their construction order. – llllllllll Mar 17 '18 at 22:50
  • 2
    As for your question, a definitive answer [here](https://wandbox.org/permlink/iEqWJHKmRHXAqoZo) – llllllllll Mar 17 '18 at 22:52
  • 1
    IMO, Google C++ Style Guide is not worth the paper it's printed on. – Jive Dadson Mar 18 '18 at 05:10
  • @liliscent - The trivially destructible rule is from the, (shudder), Google Style Guide. – Jive Dadson Mar 18 '18 at 05:14
  • 1
    The more I think about it, the more unclear this question becomes for me. Are you asking the right question? Don't you really want to know if declaring a std::mutex in a function-static variable is a Bad Thing? Of course std::mutex is not trivially destructible. At the very least, the destructor has to tell the OS it doesn't want the thing anymore. – Jive Dadson Mar 18 '18 at 05:24
  • side note: #include "foo.h" in bar.h. – Jive Dadson Mar 18 '18 at 05:28
  • Tag `void foo()` with `noexcept`. – Jive Dadson Mar 18 '18 at 05:30
  • Ah ha! Light dawns over Dunderhead. What you want to know is how to assure that the destructor for the "Bar bar" that's declared in bar.cpp is called before the destructor for foo::m that's declared in foo.cpp. So ask that. – Jive Dadson Mar 18 '18 at 05:40
  • @liliscent Thanks for the wandox link, this is great! See my edit for how trivially destructible impacts destruction order, or rather, makes destruction order irrelevant *in practice*. – Boris Dalstein Mar 18 '18 at 12:00
  • @JiveDadson See my edit for clarifications. – Boris Dalstein Mar 18 '18 at 12:00

2 Answers2

5

What the Standard Says

The answer is "no": according to the C++17 Standard, the type std::mutex is not required to have a trivial destructor. General requirements of mutex types are described in [thread.mutex.requirements], and the only paragraph describing destructibility is the following:

The mutex types shall be DefaultConstructible and Destructible. If initialization of an object of a mutex type fails, an exception of type system_error shall be thrown. The mutex types shall not be copyable or movable.

Later, the section [thread.mutex.class] details std::mutex in particular but does not specify additional requirements apart from the following paragraph:

The class mutex shall satisfy all of the mutex requirements (33.4.3). It shall be a standard-layout class (Clause 12).

Though, note that among all mutex types, std::mutex is the only one with a constexpr constructor, which is often a hint that the type might be trivially destructible as well.

What Compilers Say

(thanks @liliscent for creating the test)

#include <iostream>
#include <type_traits>
#include <mutex>
using namespace std;
int main()
{
    std::cout << boolalpha << is_trivially_destructible<mutex>::value << "\n";
}

In other words, it seems that currently, only GCC on Linux platforms provides a trivial destructor for std::mutex.

However, note that there is a Bug Request to make std::mutex trivially destructible in Clang on some platforms:

For these reasons I believe we should change 'std::mutex' to be trivially destructible (when possible). This means NOT invoking "pthread_mutex_destroy(...)" in the destructor.

I believe is a safe change on some pthread implementations. The main purpose of "pthread_mutex_destroy" is to set the lock to an invalid value, allowing use-after-free to be diagnosed. AFAIK mutex's initialized with "PTHREAD_MUTEX_INITIALIZER" own no resources and so omitting the call will not cause leaks.

On other pthread implementations this change will not be possible.

A follow-up message details that platforms where this change is possible seem to include NPTL (GLIBC) and Apple, while it doesn't seem possible on FreeBSD.

Note that the bug request also mentions the problem I was referring to in my question (emphasis mine):

A trivial destructor is important for similar reasons. If a mutex is used during dynamic initialization it might also be used during program termination. If a static mutex has a non-trivial destructor it will be invoked during termination. This can introduce the "static deinitialization order fiasco".

What Should I Do?

If you need a global mutex in portable code (for example to protect another global object such as a memory pool, etc.), and are in a use case that may be subject to the "static deinitialization order fiasco", then you need to use careful singleton techniques to ensure that the mutex is not only created before first use, but also destructed after last use (or not destructed at all).

The simplest approach is to purposefully "leak" a dynamically allocated local static mutex, like so, which is fast and most likely safe:

void foo() {
    static std::mutex* m = new std::mutex;
    std::lock_guard<std::mutex> lock(*m);
    // ...
}

Otherwise, a cleaner approach is to use the Nifty Counter idiom (or "Schwarz Counter") to control the lifetime of the mutex, although be aware that this technique introduces a small overhead at startup and termination of the program.

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
2

On VC++ std::mutex is not trivially destructible, so the answer to your question is no.

What (I think) you really want to know is how to be sure the destructor for Bar bar is called before the destructor for foo::m. Well you can't unless they are in the same translation unit. If you define them both in a file called foobar.cpp and define foo() above Bar bar, you're good.

Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • Thanks for the answer (info about behaviour on VC++). However, as you can imagine, I can't put them in the same translation unit (`Bar` is only one of the many clients of `foo()`, possibly in a different shared library). Seems like we need the Nifty Counter idiom or Trusty Leaky idiom for static mutexes, at least on some platforms. – Boris Dalstein Mar 18 '18 at 12:09
  • I accepted this answer since it does answer the question, although it'd be nice if you could include references to back the answer. This could be an extract from the standard, an implementation note, or tests such as provided by @lililescent: https://wandbox.org/permlink/iEqWJHKmRHXAqoZo. I might write another more complete answer myself if/when I find the time and courage ;) – Boris Dalstein Mar 18 '18 at 12:30
  • 1
    Thanks. I will look into it after some slumber. Bonus comment: It just occurred to me that in the lexicon of programming, "idiom" is an idiom! That is to say, it is a term whose meaning cannot be determined from common definitions and syntax. If a newb looked up "idiom" in the dictionary, he could not tell that in the programming world it means, "a somewhat tricky pattern that works". – Jive Dadson Mar 18 '18 at 12:36
  • Haha, I think I never looked at the dictionary definition of "idiom" before, I think I only used it in a programming context ;) – Boris Dalstein Mar 18 '18 at 12:41
  • What a hoot. By and large, I will use idioms until I kick the bucket. But that's neither here nor there. I took my umbrella today, because it was raining. Why my umbrella was raining, I'll never know. – Jive Dadson Mar 18 '18 at 12:46
  • FYI, I added a more complete answer, and accepted this new answer instead. I hope you don't mind :) – Boris Dalstein Mar 19 '18 at 22:25