简体   繁体   English

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

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

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

};

I have a function that I want to be executable only once for the lifetime of MyObj . 我有一个函数,我想在MyObj的生命周期中只执行一次。 There may be many instances of MyObj , and each should be able to execute that function once. MyObj可能有很多实例,每个实例应该能够执行一次该功能。 So if I have: 所以,如果我有:

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

Options: 选项:

  1. member variable: If I use a member variable, then other functions within MyObj can access it and change it. 成员变量:如果我使用成员变量,那么MyObj其他函数可以访问它并更改它。
  2. global static variable: Can't work because first,second and third will all be checking the same variable. 全局静态变量:无法工作,因为第一个,第二个和第三个都将检查相同的变量。
  3. local static: Same problem as #2. local static:与#2相同的问题。

The only solution I have found, is to have MyObj inherit from another class 我找到的唯一解决方案是让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...
        }
    } 

};

Any better suggestion ? 还有更好的建议吗?

EDIT: I don't care about thread safety! 编辑:我不关心线程安全!

EDIT: I do not want to execute the method in the constructor, simply because the method may need to be executed much later in the lifetime of the object.... 编辑:我不想在构造函数中执行该方法,只是因为该方法可能需要在对象的生命周期中稍后执行....

Use std::once_flag . 使用std::once_flag It is not resettable from other methods (then again, if you cannot trust other methods of the same class, your development process is highly questionable), easy to use, and it is even thread-safe if you ever do care about that. 它不能从其他方法重置(如果你不能相信同一类的其他方法,你的开发过程非常有问题),易于使用,如果你关心它,它甚至是线程安全的。 It can be a bit less efficient in a single-threaded program. 在单线程程序中它可能效率稍低。

#include <mutex>

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

private:
    std::once_flag initFlag;
};

I don't see what is wrong with Option 1. If a class has so many responsibilities that another function may accidentally mess with the is_init member variable then the class should probably be made smaller. 我没有看到选项1有什么问题。如果一个类有这么多的责任,另一个函数可能会意外地is_init成员变量,那么该类应该可以变小。

However, if you want to encapsulate into another class that is less error prone, rather than using inheritance, I suggest you use composition: 但是,如果你想封装到另一个不易出错的类中,而不是使用继承,我建议你使用组合:

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";
        }
    } 
};

Live demo . 现场演示

As with Option 1, you should think about what copy/move behavior do you want. 与选项1一样,您应该考虑您想要的复制/移动行为。 eg Should a copy of an initialized MyObj be considered initialized? 例如,是否应该初始化已初始化的MyObj的副本?

I see three reasonable options: 我看到三个合理的选择:

  1. Just use your option #1, a bool member variable. 只需使用您的选项#1,一个bool成员变量。
  2. Create a little class for an init flag, that can be set, but not be unset. 为init标志创建一个小类,可以设置,但不能取消设置。
  3. Use the public non-virtual interface (NVI) idiom , if you really want to be sure, that no-one messes with your flag. 使用公共非虚拟接口(NVI)习语 ,如果你真的想确定,没有人会对你的旗帜感到困惑。

A bool member variable bool成员变量

This would be my first choice. 这将是我的第一选择。 Make it private, of course. 当然,让它变得私密。 If your class has so many other data fields, that adding this new member appears painful, then this could be a sign of bad design of the entire class in the first place. 如果你的班级有这么多其他数据字段,那么添加这个新成员会显得很痛苦,那么这可能是整个班级设计糟糕的一个标志。

Often init() methods can be avoided completely by splitting up a class into two: A class A that contains the constructed data before the call to init() and a class B that is initialized upon construction. 通常可以通过将类拆分为两个来完全避免init()方法:在调用init()之前包含构造数据的类A和在构造时初始化的类B That way you can see if an object is initialized only by its type. 这样,您可以查看对象是否仅按其类型进行初始化。

An init flag that can be set, but not reset 可以设置但不能重置的init标志

This class could look somewhat like this: 这个类看起来有点像这样:

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

    operator bool() const
    {
        return isSet_;
    }

private:
    bool isSet_ = false;
};

This way, member functions cannot mess up your flag as easily. 这样,成员函数就不会轻易搞乱你的旗帜。 As an author of a class, you should be able to trust your member functions enough, that they don't set this flag, unless they are called init() . 作为类的作者,您应该能够足够信任您的成员函数,除非它们被称为init() ,否则它们不会设置此标志。

The non-virtual interface idiom 非虚拟接口习语

You create a base class with an init() function that is public and non-virtual. 您使用init()函数创建一个基类,该函数是公共和非虚拟的。 This function checks, if init() has been called before, calls a private purely virtual doInit() function which is supposed to do the actual initialization and sets the init flag after that. 如果之前调用了init() ,则此函数检查一个私有的纯虚拟doInit()函数,该函数应该进行实际初始化并在此之后设置init标志。 It looks like this: 它看起来像这样:

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

This has several security advantages: 这有几个安全优势:

  • Derived classes cannot modify isInit_ . 派生类无法修改isInit_
  • Derived classes cannot call doInit() , as long as they don't make it public or protected (which would be very nasty). 派生类不能调用doInit() ,只要它们不publicprotected (这将是非常讨厌的)。 However, they can and must implement this function. 但是,他们可以而且必须实现此功能。
  • Hence doInit() function is statically guaranteed not to be called more than once, unless an assert() will trigger. 因此,除非assert()将触发,否则静态保证doInit()函数不会被多次调用。
  • If you don't want the init() function to be public, then you can derive with the protected or the private attribute from InitializeBase . 如果您不希望init()函数是公共的,那么您可以使用InitializeBaseprotectedprivate属性派生。

The obvious drawback is that the design is more complicated and you get an additional virtual function call. 显而易见的缺点是设计更复杂,您可以获得额外的虚函数调用。 For this reason the NVI idiom has become somewhat controversial. 出于这个原因,NVI成语已经引起了一些争议。

Here's a variant that wraps a function in a class. 这是一个包含类中函数的变体。
Once the function is called, it's replaced with one that does nothing. 调用该函数后,它将替换为不执行任何操作的函数。

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's answer is pretty good and was along the same lines I was thinking. molbdnilo的答案非常好,并且与我的想法一致。 I've changed a few things which I personally think makes it more idiomatic. 我改变了一些我个人认为更具惯用性的东西。

#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();
}

The solution at the top is very good, but this might be a better solution for an interesting special case. 顶部的解决方案非常好,但对于一个有趣的特殊情况,这可能是更好的解决方案。

I assume that the method shall only be executed once because it modifies the state of the class. 我假设该方法只执行一次,因为它修改了类的状态。 For the special case that the method initializes some parts of the class, I think it is best to use an optional , either boost::optional or std::optional or std::experimental::optional , depending on what is available to you: 对于该方法初始化类的某些部分的特殊情况,我认为最好使用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