标准::互斥体是微不足道的可破坏的
Is std::mutex trivially destructible?
我有一个受互斥m
保护foo()
函数,该互斥锁被定义为foo()
的局部静态变量。我想知道在具有静态存储持续时间bar
对象的析构函数中调用foo()
是否安全:
// 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;
}
如果std::mutex
是微不足道的可破坏的,这应该是安全的,因为bar
将在m
之前被破坏。在 GCC 5.4 上,Ubuntu 16.04 调用std::is_trivially_destructible<std::mutex>::value
返回true
,所以至少在这个编译器中看起来还可以。有什么明确的答案吗?
相关:谷歌C++静态和全局变量风格指南
编辑
显然,我不够清楚,应该提供更多的背景。是的,根本问题是我希望bar
在m
之前被破坏。这是众所周知的"静态初始化惨败"的"破坏"部分,参见示例:
https://isocpp.org/wiki/faq/ctors#construct-on-first-use-v2
要点很简单:如果还有其他静态对象 析构函数可能会在 ANS 被销毁后使用 ANS,砰,你死了。 如果 a、b 和 c 的构造函数使用 ans,通常应该没问题 由于运行时系统将在静态取消初始化期间, 在这三个对象中的最后一个被销毁后销毁 ans。 但是,如果 a 和/或 b 和/或 c 在其构造函数中无法使用 ans 和/或如果任何地方的任何代码获取 ans 的地址并将其交给 其他一些静态对象,所有赌注都关闭了,你必须非常, 非常小心。
这就是为什么谷歌建议不要使用静态对象,除非它们是微不足道的可破坏的。问题是,如果物体是微不足道的可破坏的,那么破坏的顺序并不重要。即使m
在bar
之前被"析构",您仍然可以在实践中在bar
的析构函数中使用m
而不会使程序崩溃,因为析构函数实际上什么都不做(它不会释放任何内存或释放任何其他类型的资源)。
事实上,如果m
是微不足道的可破坏的,那么程序甚至可能根本不会破坏m
,这实际上确保了m
在bar
或任何其他静态物体之后被"破坏",这些物体不是平凡可破坏的。例如,请参阅:
http://en.cppreference.com/w/cpp/language/lifetime#Storage_reuse
不需要程序来调用对象的析构函数来结束 如果对象是微不足道的可破坏的,或者如果程序是可破坏的,则其生存期 不依赖于析构函数的副作用。
由于这些原因,如果您的单例是微不足道的可破坏的,那么使用复杂的单例习语(例如 Nifty Counter 习语)实际上是矫枉过正的。
换句话说,如果std::mutex
是微不足道的可破坏的,那么我上面的代码示例是安全的:m
要么在bar
后被破坏,要么在bar
之前被"技术上破坏",但无论如何都不会导致崩溃。但是,如果std::mutex
不是微不足道的可破坏的,那么我可能需要使用 Nifty Counter 习语,或者使用更简单但"故意泄漏"的 Trusty Leaking 成语。
相关:
- https://stackoverflow.com/a/335746/1951907
- https://stackoverflow.com/a/17712497/1951907
标准怎么说
答案是否定的:根据 C++17 标准,std::mutex
型不需要具有微不足道的析构函数。[thread.mutex.requirements] 中描述了互斥类型的一般要求,描述可破坏性的唯一段落如下:
互斥锁类型应为"默认可构造"和"可破坏"。如果 初始化互斥锁类型的对象失败,异常 应抛出system_error型。互斥锁类型不得 可复制或可移动。
稍后,[thread.mutex.class] 部分特别详细介绍了std::mutex
,但除了以下段落外,没有指定其他要求:
应满足所有互斥锁要求(33.4.3)。 它应为标准布局类(第12条)。
但是,请注意,在所有互斥锁类型中,std::mutex
是唯一具有constexpr
构造函数的类型,这通常暗示该类型也可能是微不足道的可破坏的。
编译器怎么说
(感谢@liliscent创建测试)
#include <iostream>
#include <type_traits>
#include <mutex>
using namespace std;
int main()
{
std::cout << boolalpha << is_trivially_destructible<mutex>::value << "n";
}
- 使用 Clang 运行 7.0.0:
false
- 使用 GCC 8.0.1 运行:
true
- 使用 MSVC 版本 19.00.23506 运行:
false
换句话说,目前似乎只有Linux平台上的GCC为std::mutex
提供了一个微不足道的析构函数。
但是,请注意,在某些平台上,有一个错误请求可以使std::mutex
在Clang中变得微不足道的可破坏:
出于这些原因,我认为我们应该将"std::mutex"更改为 微不足道的可破坏性(如果可能)。这意味着不调用 析构函数中的"pthread_mutex_destroy(...)"。
我相信这是对某些 pthread 实现的安全更改。主要 "pthread_mutex_destroy"的目的是将锁设置为无效 值,允许诊断释放后使用。AFAIK 互斥锁 初始化为"PTHREAD_MUTEX_INITIALIZER"没有资源等 省略调用不会导致泄漏。
在其他 pthread 实现上,此更改将是不可能的。
后续消息详细说明了可能进行此更改的平台似乎包括NPTL(GLIBC)和Apple,而在FreeBSD上似乎是不可能的。
请注意,错误请求还提到了我在问题中提到的问题(强调我的):
出于类似的原因,一个微不足道的析构函数很重要。如果互斥锁 在动态初始化期间使用 它也可以在 程序终止。如果静态互斥锁具有非平凡析构函数,则 将在终止期间调用。这可以引入"静态" 取消初始化顺序惨败"。
我该怎么办?
如果你在可移植代码中需要一个全局互斥锁(例如保护另一个全局对象,如内存池等),并且处于可能受到"静态解初始化顺序惨败"影响的用例中,那么你需要使用谨慎的单例技术来确保互斥锁不仅在首次使用之前创建, 但在最后一次使用后也会被破坏(或根本没有被破坏)。
最简单的方法是有目的地"泄漏"动态分配的本地静态互斥锁,如下所示,这既快速又很可能是安全的:
void foo() {
static std::mutex* m = new std::mutex;
std::lock_guard<std::mutex> lock(*m);
// ...
}
否则,更简洁的方法是使用 Nifty 计数器习惯用语(或"Schwarz 计数器")来控制互斥锁的生存期,但请注意,此技术在程序启动和终止时引入了少量开销。
在 VC++ 上 std::mutex 不是微不足道的可破坏的,所以你的问题的答案是否定的。
(我认为)您真正想知道的是如何确保在foo::m
的析构函数之前调用Bar bar
析构函数。 好吧,除非它们在同一个翻译单元中,否则您不能。如果你在一个名为 foobar 的文件中定义它们.cpp并在 Bar bar 上方定义 foo(),那就很好了。
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 内置函数可查看CPP中的成员变量
- 可组合的lambda/std::函数与std::可选
- 如何使用Crypto++并为RSA返回可打印的字节/字符数组
- 使用gcc从静态链接的文件中查找可选符号
- 构建可组合有向图(扫描仪生成器的汤普森构造算法)
- 在调用其析构函数之前,是否有任何实际理由检查某些东西是否可破坏?
- 为什么 std::atomic<std::string> 会给出微不足道的可复制错误?
- 什么时候 std::initializer_list 是微不足道的可构造的?
- 可破坏的网格块会导致奇怪的碰撞
- 为什么一对常量是微不足道的可复制的,而对不是?
- 在一个微不足道的可复制结构中,移动语义应该实现吗?
- 标准::互斥体是微不足道的可破坏的
- 不能让类是微不足道的可复制的。我做错了什么?
- 什么时候可以安全地重复使用来自微不足道的可破坏对象的内存而不进行洗涤
- STD ::可选的微不足道默认构造函数
- C++11通过引用捕获的lambdas一般是可破坏的
- 为什么异常总是在具有可破坏堆栈对象的非叶函数中产生开销
- C/C++ 中的可破坏命名作用域
- const int[2] 是微不足道的可复制的