是否可以使std :: once_flag成为成员(非静态成员)

时间:2015-11-05 21:10:11

标签: c++ c++11

我需要懒惰地进行一些计算并缓存当前对象的结果(首次请求时)。我显然可以用互斥量来做,但我只是好奇可以让std::once_flag成为一个成员并使用它来初始化这个实例。我用它来做单身,但不用于普通物品。

1 个答案:

答案 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成员将对对象进行复制或移动,因此您也将受到限制。防止这种情况。

因此,如果需要复制或移动对象,则可能需要使用其他选项。