我偶然发现了一种我认为非常简单的方法,可以在不知不觉中击中自己的脚。
先简单介绍一下
数据成员的初始化顺序是数据成员的声明顺序。所以这是非法的:
struct A
{
std::size_t i_;
std::size_t length_;
A(std::size_t length)
: i_{length_} // UB here. `length_` is uninitialized
length_{length}
{}
};
因为数据成员length_在i_. 幸运的是,gcc并clang为此给出了非常好的警告。简单的解决方案是从参数初始化每个数据成员,即i_{length}.
现在到重点
但是,当它不是立即显而易见时又如何呢?例如,当数据成员是std::thread
struct X
{
std::thread thread_;
std::mutex mutex_;
X() : thread_{&X::worker_thread, this}
{}
auto worker_thread() -> void
{
// use mutex_
std::lock_guard lk{mutex_}; // boom?
// ..
}
};
使用数据成员初始化器时也会出现同样的情况:
struct X
{
std::thread thread_{&X::worker_thread, this};
std::mutex mutex_;
};
这看起来很无辜,也没有gcc警告clang这种情况。这并不奇怪,因为依赖是隐藏的。
我会想象上述情况并不少见,所以我正在确认这确实是 UB。最后声明std::mutex数据成员,或者默认初始化并稍后分配。