繁体   English   中英

如何确保方法只在该对象的生命周期内执行一次?

[英]How to ensure that a method is executed only once for the lifetime of that object?

class MyObj{
public:
    void myFunc(){
         //ToBeExecutedJustOnce
    }

};

我有一个函数,我想在MyObj的生命周期中只执行一次。 MyObj可能有很多实例,每个实例应该能够执行一次该功能。 所以,如果我有:

MyObj first;
MyObj second;
MyObj third:
first.myFunc(); // Should execute
second.myFunc(); // Should execute
third.myFunc(); // Should execute
first.myFunc(); // Should not execute
second.myFunc(); // Should not execute
third.myFunc(); // Should not execute

选项:

  1. 成员变量:如果我使用成员变量,那么MyObj其他函数可以访问它并更改它。
  2. 全局静态变量:无法工作,因为第一个,第二个和第三个都将检查相同的变量。
  3. local static:与#2相同的问题。

我找到的唯一解决方案是让MyObj继承自另一个类

MyOtherObj{
private:
    bool _isInit = false;
public:
    bool isInit(){
          bool ret = true;
          if (!_isInit){
              ret = false;
              _isInit = true;
          }
          return ret;
    }
};

class MyObj : public MyOtherObj {
public:
    void MyFunc(){
        if (!isInit()){
            //Do stuff...
        }
    } 

};

还有更好的建议吗?

编辑:我不关心线程安全!

编辑:我不想在构造函数中执行该方法,只是因为该方法可能需要在对象的生命周期中稍后执行....

使用std::once_flag 它不能从其他方法重置(如果你不能相信同一类的其他方法,你的开发过程非常有问题),易于使用,如果你关心它,它甚至是线程安全的。 在单线程程序中它可能效率稍低。

#include <mutex>

class MyObj {
public:
    void MyFunc() {
        std::call_once(initFlag, [=] {
                //Do stuff...
            });
    }

private:
    std::once_flag initFlag;
};

我没有看到选项1有什么问题。如果一个类有这么多的责任,另一个函数可能会意外地is_init成员变量,那么该类应该可以变小。

但是,如果你想封装到另一个不易出错的类中,而不是使用继承,我建议你使用组合:

class FirstTime {
    bool first_time = true;
public:
    bool operator()(){
          if (!first_time) 
              return false;
          first_time = false;
          return true;
    }
};

class MyObj {
    FirstTime first_time;
public:
    void myFunc(){
        if (first_time()){
            std::cout << "First time!\n";
        }
    } 
};

现场演示

与选项1一样,您应该考虑您想要的复制/移动行为。 例如,是否应该初始化已初始化的MyObj的副本?

我看到三个合理的选择:

  1. 只需使用您的选项#1,一个bool成员变量。
  2. 为init标志创建一个小类,可以设置,但不能取消设置。
  3. 使用公共非虚拟接口(NVI)习语 ,如果你真的想确定,没有人会对你的旗帜感到困惑。

bool成员变量

这将是我的第一选择。 当然,让它变得私密。 如果你的班级有这么多其他数据字段,那么添加这个新成员会显得很痛苦,那么这可能是整个班级设计糟糕的一个标志。

通常可以通过将类拆分为两个来完全避免init()方法:在调用init()之前包含构造数据的类A和在构造时初始化的类B 这样,您可以查看对象是否仅按其类型进行初始化。

可以设置但不能重置的init标志

这个类看起来有点像这样:

class InitFlag
{
public:
    void set()
    {
        isSet_ = true;
    }

    operator bool() const
    {
        return isSet_;
    }

private:
    bool isSet_ = false;
};

这样,成员函数就不会轻易搞乱你的旗帜。 作为类的作者,您应该能够足够信任您的成员函数,除非它们被称为init() ,否则它们不会设置此标志。

非虚拟接口习语

您使用init()函数创建一个基类,该函数是公共和非虚拟的。 如果之前调用了init() ,则此函数检查一个私有的纯虚拟doInit()函数,该函数应该进行实际初始化并在此之后设置init标志。 它看起来像这样:

class InitializeBase
{
public:
    virtual ~InitializeBase() = default;

    bool isInit() const
    {
        return isInit_;
    }

    void init()
    {
        assert( !isInit() );
        doInit();
        isInit_ = true;
    }

private:
    virtual void doInit() = 0;

    bool isInit_ = false;
};

这有几个安全优势:

  • 派生类无法修改isInit_
  • 派生类不能调用doInit() ,只要它们不publicprotected (这将是非常讨厌的)。 但是,他们可以而且必须实现此功能。
  • 因此,除非assert()将触发,否则静态保证doInit()函数不会被多次调用。
  • 如果您不希望init()函数是公共的,那么您可以使用InitializeBaseprotectedprivate属性派生。

显而易见的缺点是设计更复杂,您可以获得额外的虚函数调用。 出于这个原因,NVI成语已经引起了一些争议。

这是一个包含类中函数的变体。
调用该函数后,它将替换为不执行任何操作的函数。

const std::function<void()> nop = [](){};

class Once
{
public:
    Once(std::function<void()> f) : m_function(f) {}
    void operator()()
    {
        m_function();
        m_function = nop;
    }    
private:
    std::function<void()> m_function;
};

class Foo
{
public:
    Foo(int x) 
        : m_function([this](){m_x += 1;}), 
          m_x(x) {}
    int get() const { return m_x; }
    void dostuff() { m_function(); }
private:
    int m_x;
    Once m_function;
};

int main()
{
    Foo f(0);
    cout << f.get() << endl; // 0
    f.dostuff();
    cout << f.get() << endl; // 1
    f.dostuff();
    cout << f.get() << endl; // 1
}

molbdnilo的答案非常好,并且与我的想法一致。 我改变了一些我个人认为更具惯用性的东西。

#include <iostream>
#include <functional>

class Once
{
    bool isDone = false;    
public:
    void exec(std::function<void()> function)
    {
        if (!isDone)
        {
            function();
            isDone = true;
        }
    }
};

class MyObj {
    Once once = Once();

public:
    void myFunc()
    {
        once.exec( []{
            std::cout << "Hello, world!";
            // do some stuff
        });
    } 
};

int main()
{
    MyObj foo = MyObj();
    foo.myFunc();
    foo.myFunc();
    foo.myFunc();
}

顶部的解决方案非常好,但对于一个有趣的特殊情况,这可能是更好的解决方案。

我假设该方法只执行一次,因为它修改了类的状态。 对于该方法初始化类的某些部分的特殊情况,我认为最好使用optional boost::optionalstd::optionalstd::experimental::optional ,具体取决于您可以使用的内容:

#include <boost/optional.hpp>

class X
{
    public:
        void init()
        {
            if( ! _x )
            {
                _x.reset( 5 );
            }
        }

    private:
        boost::optional<int> _x;
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM