我需要懒惰地进行一些计算并缓存当前对象的结果(首次请求时)。我显然可以用互斥量来做,但我只是好奇可以让std::once_flag
成为一个成员并使用它来初始化这个实例。我用它来做单身,但不用于普通物品。
答案 0 :(得分:1)
做您建议的事肯定是可能。只要once_flag
在尝试使用它之前被正确构造(a),它应该可以正常工作,并且可以通过将其作为类中的成员变量来保证这一点,例如:
#include <iostream>
#include <mutex>
class MyClass {
public:
MyClass() {
std::cout << "Constructing\n";
}
void DoSomething() {
std::call_once(m_lazyInit, []() {
std::cout << "Lazy init\n";
});
std::cout << "Doing something\n";
}
private:
std::once_flag m_lazyInit;
};
int main() {
MyClass x;
x.DoSomething(); // Sequential but would also work concurrently.
x.DoSomething();
return 0;
}
在此之后的任何时候,无论您调用DoSomething()
的次数如何,都只会为您的对象成功调用一次懒惰的初始化代码:
Constructing
Lazy init
Doing something
Doing something
似乎,从您的评论来看,您担心它会占用大量资源,并且使用诸如静态(类级别)之类的东西可能会 费劲)与成员布尔值的互斥量执行相同的操作。这样,可能重负荷的线程工作被限制为每个对象一个项目,而不是一个项目(仅替换上面代码中的类):
class MyClass {
public:
MyClass() : m_lazyInitFlag(true) {
std::cout << "Constructing\n";
}
void DoSomething() {
{ // Limit lock duration to as short as possible.
std::lock_guard<std::mutex> lazyInit(m_lazyInitMutex);
if (m_lazyInitFlag) {
std::cout << "Lazy init\n";
m_lazyInitFlag = false;
}
}
std::cout << "Doing something\n";
}
private:
static std::mutex m_lazyInitMutex;
bool m_lazyInitFlag;
};
std::mutex MyClass::m_lazyInitMutex;
但是,我会对此感到少。将您的标准库放到一起的人们将对其进行最大程度的优化,并且几乎可以肯定地考虑了您需要按对象call-once()
调用的可能性。
此外,类似static-mutex-member-flag选项的解决方案引入了 more 争用,因为您只能在整个类中运行一个并发可调用对象,而不能在每个对象中运行一个并发调用对象。
这也意味着,如果您希望使用会失败的可调用对象,则需要使用try..catch
块自己处理,而不是call_once()
提供的自动处理。 / p>
我的建议是仅使用call_once()
功能,因为这确实是其预期的用例。仅担心可能出现的性能问题,然后,您可以研究其他解决方案。
换句话说,YAGNI:-)
(a)正如Barry在评论中指出的那样,由于不可复制,不可移动的once_flag
成员将对对象进行复制或移动,因此您也将受到限制。防止这种情况。
因此,如果需要复制或移动对象,则可能需要使用其他选项。