【发布时间】:2018-03-17 22:15:45
【问题描述】:
我有一个函数foo() 受互斥锁m 保护,该互斥锁定义为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,所以至少在这个编译器中看起来没问题。有明确的答案吗?
相关:Static and Global Variables 上的 Google C++ 样式指南
编辑
显然,我不够清楚,应该提供更多上下文。是的,根本问题是我希望bar 在m 之前被破坏。这是众所周知的“静态初始化惨败问题”的“破坏”部分,例如:
https://isocpp.org/wiki/faq/ctors#construct-on-first-use-v2
要点很简单:如果有任何其他静态对象 析构函数可能会在 ans 被破坏后使用 ans,砰,你死定了。 如果a、b、c的构造函数使用ans,一般应该没问题 因为运行时系统将在静态反初始化期间, destruct ans 在这三个对象中的最后一个被破坏之后。 但是,如果 a 和/或 b 和/或 c 未能在其构造函数中使用 ans 和/或如果任何地方的任何代码获得 ans 的地址并将其交给 其他一些静态对象,所有的赌注都没有了,你必须非常, 非常小心。
这就是为什么 Google 不建议使用静态对象的原因,除非它们很容易被破坏。问题是,如果对象可以简单地破坏,那么破坏的顺序并不重要。即使m 在bar 之前被“破坏”,您仍然可以在bar 的析构函数中使用m 而不会导致程序崩溃,因为析构函数实际上什么都不做(它不会释放任何内存或释放任何其他类型的资源)。
事实上,如果m 可以简单地破坏,那么程序甚至可能根本无法破坏m,这实际上确保m 在bar 或任何其他静态对象之后被“破坏”不可轻易破坏。例如:
http://en.cppreference.com/w/cpp/language/lifetime#Storage_reuse
程序不需要调用对象的析构函数来结束 如果对象是可简单破坏的或者程序的生命周期 不依赖析构函数的副作用。
由于这些原因,如果您的单例可以简单地破坏,则使用复杂的单例习语(例如 Nifty Counter idiom)实际上是矫枉过正。
换句话说,如果std::mutex 可以简单地破坏,那么我上面的代码示例是安全的:m 在bar 之后被破坏,或者在bar 之前被“技术破坏”但不会导致无论如何都会崩溃。但是,如果 std::mutex 不是可以轻易破坏的,那么我可能需要使用 Nifty Counter 成语,或者更简单但“故意泄漏”Trusty Leaking idiom。
相关:
【问题讨论】:
-
这句话值得关注“被普通可破坏对象占用的存储空间可以在不调用析构函数的情况下被重用。”来自:en.cppreference.com/w/cpp/types/is_destructible猜这取决于实现是否需要/需要释放
native_handle -
Related。似乎暗示“不”。
-
标准说是
is_destructable,而不是is_trivially_destructable。看起来更轻松了。 -
我不明白你的意思。平凡破坏冲击破坏秩序如何?静态持续时间销毁顺序与其构造顺序相反。
-
至于你的问题,一个确定的答案here