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

Kai*_*zay 20 c++ c++11

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

};
Run Code Online (Sandbox Code Playgroud)

我有一个函数,我想在生命周期中只执行一次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
Run Code Online (Sandbox Code Playgroud)

选项:

  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...
        }
    } 

};
Run Code Online (Sandbox Code Playgroud)

还有更好的建议吗?

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

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

Arn*_*gel 47

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

#include <mutex>

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

private:
    std::once_flag initFlag;
};
Run Code Online (Sandbox Code Playgroud)

  • 啊,上面评论中提到的问题是`std :: once_flag`不可复制/移动. (3认同)
  • @FelixDombek:重点是无法手动写入标志.但是,对于需要这种行为的对象,赋值甚至意味着什么呢? (3认同)

Chr*_*rew 22

我没有看到选项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";
        }
    } 
};
Run Code Online (Sandbox Code Playgroud)

现场演示.

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

  • @Michael:的确请解释原因.这可能是因为你希望所有东西都尽可能隐含,因此认为它应该由`operator bool()`或`operator void*()`或其他东西来完成,所以用户写`if(first_time)`.或者可能是因为你希望所有内容都尽可能明确,因此认为用户应该编写`if(first_time.is_the_first_time())`.或者是其他东西. (7认同)
  • @Michael我发现变量/类名和方法中"第一次"的重复非常难看.我更喜欢尤达的演讲:`first_time.is_it("?")`. (4认同)
  • @Michael请解释原因. (3认同)
  • 我非常不同意使用`operator()`. (2认同)
  • 像`operator()`vs`is_the_first_time()`这样的编码标准肯定超出了问题的范围.将`FirstTime`作为仿函数对象是否有意义取决于此示例中未显示的许多因素(例如,如果您有理由将其传递给处理仿函数或函数指针的函数,则可以证明使用`操作符()`) (2认同)

Ral*_*zky 5

我看到三个合理的选择:

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

bool成员变量

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

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

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

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

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

    operator bool() const
    {
        return isSet_;
    }

private:
    bool isSet_ = false;
};
Run Code Online (Sandbox Code Playgroud)

这样,成员函数就不会轻易搞乱你的旗帜.作为类的作者,您应该能够完全信任您的成员函数,除非它们被调用,否则它们不会设置此标志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;
};
Run Code Online (Sandbox Code Playgroud)

这有几个安全优势:

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

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