如何确保方法在该对象的生存期内仅执行一次?
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
选项:
成员变量- :如果我使用成员变量,那么
MyObj
中的其他函数可以访问它并更改它。 - 全局静态变量:无法工作,因为第一个,第二个和第三个都将检查同一个变量。
- 本地静态:与#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,一个
bool
成员变量。 - 为 init 标志创建一个小类,可以设置,但不能取消设置。
- 使用公共非虚拟接口(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()
,只要它们不让它public
或protected
(这将是非常讨厌的)。但是,他们可以并且必须实现此功能。 - 因此
doInit()
静态保证函数不会被多次调用,除非触发assert()
。 - 如果您不希望
init()
函数是公共的,则可以使用protected
或private
属性从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();
}
顶部的解决方案非常好,但对于一个有趣的特殊情况来说,这可能是一个更好的解决方案。
我假设该方法只执行一次,因为它修改了类的状态。对于该方法初始化类的某些部分的特殊情况,我认为最好使用optional
,boost::optional
或std::optional
或std::experimental::optional
,具体取决于您可以使用的内容:
#include <boost/optional.hpp>
class X
{
public:
void init()
{
if( ! _x )
{
_x.reset( 5 );
}
}
private:
boost::optional<int> _x;
};
- 为什么我的信号处理程序只执行一次?
- For循环在cpp中仅执行一次
- 在可变函数调用中执行一次语句
- cppUnit:针对多个测试方法执行一次的设置函数
- For 循环是否总是至少执行一次?
- 如何确保方法在该对象的生存期内仅执行一次?
- 线程仅执行一次
- while(getline(myReadFile, temp, ':')) 执行一次迭代太多导致向量越界
- Qt - 解决一个插槽上的两个顺序调用,并且仅执行一次操作
- 函数执行一次
- 共享/独占锁定,其中共享工作只能执行一次
- C++执行一次序列的最佳方法
- Linux/c++ 定时方法,尽管有漂移,但每 N 秒执行一次
- 每 t 毫秒执行一次 c++ 代码
- Winsock:回显服务器回复两次,当时我刚刚将其编程为执行一次send()调用
- 我在循环时遇到困难。它只执行一次
- QTimer 每秒执行一次方法
- 阿西奥:为什么计时器只执行一次
- 每秒执行一次的函数,它是有效的,但喜欢每0.5秒执行一次
- 在没有默认构造函数的OpenMP中,每个线程执行一次代码