我有以下
thread_local
单例代码:
struct Singl {
int& ref;
Singl(int& r) : ref(r) {}
~Singl() {}
void print() { std::cout << &ref << std::endl; }
};
static auto& singl(int& r) {
static thread_local Singl i(r);
return i;
}
int main() {
int x = 4;
singl(x).print();
int y = 55;
singl(y).print();
return 0;
}
此程序打印两次对
x
的引用。
编译器(godbolt 上的 gcc 8.1)似乎对单例对象进行了延迟初始化:
singl(int&):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR fs:0
add rax, OFFSET FLAT:guard variable for singl(int&)::i@tpoff
movzx eax, BYTE PTR [rax]
test al, al
jne .L5
mov rax, QWORD PTR [rbp-8]
mov rdx, QWORD PTR fs:0
add rdx, OFFSET FLAT:singl(int&)::i@tpoff
mov rsi, rax
mov rdi, rdx
call Singl::Singl(int&)
mov rax, QWORD PTR fs:0
add rax, OFFSET FLAT:guard variable for singl(int&)::i@tpoff
mov BYTE PTR [rax], 1
mov rax, QWORD PTR fs:0
add rax, OFFSET FLAT:singl(int&)::i@tpoff
mov edx, OFFSET FLAT:__dso_handle
mov rsi, rax
mov edi, OFFSET FLAT:_ZN5SinglD1Ev
call __cxa_thread_atexit
.L5:
mov rax, QWORD PTR fs:0
add rax, OFFSET FLAT:singl(int&)::i@tpoff
leave
ret
这是每当我多次调用传递不同参数的
singl
函数时我可以预期的默认行为吗?或者是否有可能在后续调用中第二次初始化单例对象?
这确实是保证。当控制到达声明时,static/thread_local 局部变量只会被初始化一次。
需要注意的几点:
如果多个线程同时调用该函数,则只有一个线程会执行初始化,其他线程会等待。这就是反汇编中的保护变量所做的事情。
如果初始化抛出异常,则认为初始化不完整,并且将在下次控制到达时再次执行。
换句话说,他们_只是工作_™。
并不是真正的单例,因为你有与线程一样多的实例。
检查输出:
int main() {
auto t1 = std::thread([]{
int x = 4;
singl(x).print();
});
auto t2 = std::thread([]{
int y = 55;
singl(y).print();
});
t1.join();
t2.join();
return 0;
}
它违背了单例的目的。
在我看来,当向OOP的思维转变还没有完全完成,并且程序员仍然感受到使用全局变量的冲动时,单例就会出现。