如何确保方法在该对象的生存期内仅执行一次?

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

本文关键字:执行 一次 生存期 对象 确保 何确保 方法      更新时间:2023-10-16
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. 本地静态:与#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成员变量,那么这个类可能应该变得更小。

但是,如果你想封装到另一个不太容易出错的类中,而不是使用继承,我建议你使用 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";
}
} 
};

现场演示。

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

我看到三个合理的选择:

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

bool成员变量

这将是我的首选。当然,将其设为私有。如果您的类有太多其他数据字段,以至于添加此新成员看起来很痛苦,那么这可能首先表明整个类的设计很糟糕。

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

可以设置但不能重置的初始化标志

这个类可能看起来像这样:

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(这将是非常讨厌的)。但是,他们可以并且必须实现此功能。
  • 因此doInit()静态保证函数不会被多次调用,除非触发assert()
  • 如果您不希望init()函数是公共的,则可以使用protectedprivate属性从InitializeBase派生。

明显的缺点是设计更复杂,并且您会收到额外的虚拟函数调用。出于这个原因,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();
}

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

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

#include <boost/optional.hpp>
class X
{
public:
void init()
{
if( ! _x )
{
_x.reset( 5 );
}
}
private:
boost::optional<int> _x;
};