美文网首页
std::call_once的使用

std::call_once的使用

作者: 琼蘂无徵朝霞难挹 | 来源:发表于2019-03-16 20:25 被阅读0次

一、背景

有时候我们需要在第一次执行某个函数时进行一个特定的操作specifiedOperation,后面就不再执行specifiedOperation了,那么该怎么办。

二、如何解决

0)example

class Example{
    public:
        Example()=default;
    private:
        void specifiedOperation();
}

1)简单想法

一般我们会这么想:定义一个non-local的变量,比如说bool isInvoked_=false;,然后在执行specifiedOperation()时将该变量置为true,函数定义大致如下:

class Example{
    public:
        Example()=default;
    private:
        void doSomething();
    private:
        bool isInvoked_=false;
}
void Example::doSomething(){
    if(isInvoked_){
        specifiedOperation();
    }
    isInvoked_=true;
}

这样第二次调用就不会在调用specifiedOperation()了。

3)标准库为我们提供的方法std::call_once

1.用法

在标准库里有一个函数同样可以实现这个功能(定义于mutex),先来看一下它的签名:

template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );

他接受的第一个参数类型为std::once_flag,它只用默认构造函数构造,不能拷贝不能移动,表示函数的一种内在状态。后面两个参数很好理解,第一个传入的是一个Callable,如果对于什么是Callable不了解的,可以去cppreference上查找。Callable简单来说就是可调用的东西,大家熟悉的有函数、函数对象(重载了operator()的类)、std::function和函数指针,C++11新标准中还有std::bind和lambda,不熟悉的自行查阅。最后一个参数就是你要传入的参数。

在使用的时候我们只需要定义一个non-local的std::once_flag,在调用时传入参数即可,如下所示:

class Example{
    public:
        Example()=default;
    private:
        void doSomething();
    private:
        std::once_flag isInvoked_;
}
void Example::doSomething(){
    std::call_once(&Example::specifiedOperation,this);
}
2.内部细节

该函数传入的std::ince_flag其实可以理解为上面我们想说的简单方法,它在调用传入的可调用对象时,如果该调用成功返回了没有抛出异常,那么他就会改变std::once_flag对象的内部状态,下次调用std::call_once时会首先检查std::once_flag,如果状态已经改变了,他就不会调用传入的可调用对象。std::call_once在签名设计时也很好地考虑到了参数传递的开销问题,可以看到,不管是Callable还是Args,都使用了&&作为形参。他使用了一个template中的reference fold,简单分析:

  1. 如果传入的是一个右值,那么Args将会被推断为Args
  2. 如果传入的是一个const左值,那么Args将会被推断为const Args&
  3. 如果传入的是一个non-const的左值,那么Args将会被推断为Args&

也就是说,不管你传入的参数是什么,最终到达std::call_once内部时,都会是参数的引用(右值引用或者左值引用),所以说是零拷贝的。那么还有一步呢,我们还得把参数传到可调用对象里面执行我们要执行的函数,这一步同样做到了零拷贝,这里用到了另一个标准库的技术std::forward,看一下它的一个签名:

template< class T >
T&& forward( typename std::remove_reference<T>::type& t ) noexcept;

参数在传递到callable对象时大概是这样的std::forward<Args>(args),模板参数T已经在传入参数到std::call_once时确定了,forward做出了这样的承诺,如果传入的Args是一个Args,那么他将返回右值引用,如果传入的是const Args&,那么也会返回一个const Args&,如果传入的是Args&,那么也会返回一个Args,所以这些参数在传入callable是同样是一个引用,这就是所谓的perfect forward。

所以说使用std::call_once时在参数传递方面是零拷贝的,它与std:thread不一样,因为std::call_once在执行函数时并没有另开线程。

其实我们在用的时候没有必要一步一步的去分析,我们只要知道,使用std::forward配合模板的reference fold,就可以实现参数传递的零拷贝。

3.注意

一旦调用的函数抛出了异常,那么下次执行std::call_once时不会跳过,而是会再次尝试,知道函数执行成功,不抛出异常为止。

三、总结

如果你的函数操作不会产生异常,那么可以使用std::call_once,使得代码更加的安全简洁易读。

相关文章

  • C++ 并发编程学习(九)

    保护共享数据的替代设施 一. std::once_flag 和 std::call_once 二. 一个 std:...

  • std::call_once的使用

    一、背景 有时候我们需要在第一次执行某个函数时进行一个特定的操作specifiedOperation,后面就不再执...

  • 保证函数只被调用一次

    在做一些资源初始化的时候,我们要保证资源初始化的函数只被调用一次,这时候可以使用std::call_once #i...

  • 2021-01-21

    C++11std::call_once的作用是很简单的,就是保证函数或者一些代码段在并发或者多线程的情况下,始终只...

  • C++基础知识

    std是指名称空间,using namespace std; 是指使用名称空间std 位的取值为0,1 使用aut...

  • C++多线程std::thread

    std::thread 在 头文件中声明,因此使用 std::thread 时需要包含 头文件。 std:...

  • C++11 实现链式调用

    技术点: 使用std::function实现函数调用 使用std::result_of获取后一个函数的返回值 使用...

  • std::async

    先说明一点:std::async是std::future的高级封装, 一般我们不会直接使用std::futrue,...

  • 转载--std::ref应用

    在std::promise范例中,使用了std::ref将future对象传递给引用参数类型的任务函数。 std:...

  • 再说智能指针

    一 STL的智能指针及使用 STL中智能指针有std::shared_ptr std::weak_ptr std:...

网友评论

      本文标题:std::call_once的使用

      本文链接:https://www.haomeiwen.com/subject/ymosmqtx.html