Подтвердить что ты не робот

Как обеспечить, чтобы метод выполнялся только один раз для жизни этого объекта?

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 могут получить к ней доступ и изменить ее.
  • глобальная статическая переменная: не может работать, потому что первая, вторая и третья все будут проверять одну и ту же переменную.
  • 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...
        }
    } 

};

Любое лучшее предложение?

EDIT: я не забочусь о безопасности потоков!

EDIT: я не хочу выполнять метод в конструкторе просто потому, что этот метод, возможно, потребуется выполнить намного позже в течение жизни объекта....

4b9b3361

Ответ 1

Я не вижу, что не так с Вариантом 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?

Ответ 2

Используйте std::once_flag. Это не сбрасывается с других методов (опять же, если вы не можете доверять другим методам одного и того же класса, ваш процесс разработки очень сомнительный), прост в использовании и даже поточно-безопасен, если вы когда-нибудь позаботитесь об этом. Это может быть немного менее эффективным в однопоточной программе.

#include <mutex>

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

private:
    std::once_flag initFlag;
};

Ответ 3

Я вижу три разумных варианта:

  • Просто используйте свою опцию # 1, переменную-член bool.
  • Создайте небольшой класс для флага инициализации, который можно установить, но не удаляться.
  • Используйте общедоступный не виртуальный интерфейс (NVI) идиома, если вы действительно хотите быть уверенным, что никто не путается с вашим флаг.

A bool переменная-член

Это был бы мой первый выбор. Сделайте это частным, конечно. Если ваш класс имеет так много других полей данных, что добавление этого нового члена кажется болезненным, это может быть признаком плохого дизайна всего класса в первую очередь.

Часто методы init() можно полностью исключить, разделив класс на два: класс A, который содержит построенные данные перед вызовом init() и класс B, который инициализируется при построении. Таким образом вы можете увидеть, инициализирован ли объект только по типу.

Флаг инициализации, который можно установить, но не reset

Этот класс может выглядеть примерно так:

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 стала несколько противоречивой.

Ответ 4

Здесь вариант, который обертывает функцию в классе.
Как только функция вызывается, она заменяется на ту, которая ничего не делает.

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
}

Ответ 5

molbdnilo answer довольно хорош и был в том же духе, о котором я думал. Я изменил некоторые вещи, которые, как я лично думаю, делает его более идиоматичным.

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

Ответ 6

Решение наверху очень хорошее, но это может быть лучшим решением для интересного частного случая.

Я предполагаю, что метод должен выполняться только один раз, поскольку он изменяет состояние класса. В частном случае, когда метод инициализирует некоторые части класса, я думаю, что лучше использовать 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;
};