2014-02-17 2 views
1

Есть ли способ заставить количество исполнений функции-члена одному? Для того, чтобы объяснить, позволяет сказать, что у меня естьОдин раз в жизни

class foo { 
public: 
    void Once(); 
}; 

Я хочу каждый экземпляр Foo, чтобы вызвать функцию член Foo :: После того, как() ровно один раз. Если вызов был сделан, чтобы снова запретить вызов, и если звонок не был сделан, вызовите его перед уничтожением.

  • Есть ли идиома или шаблон дизайна, связанный с этим?
  • Является ли жестко закодированное решение по размещению члена foo::was_called_flag (который я установил бы 0 или 1), допустимо или есть какие-либо уязвимости?
  • Есть ли решение, которое масштабируется хорошо произвольное число казней
+3

использовать счетчик. что вы хотите, когда вызывающий нарушает ваши правила? – thang

+4

... конструктор вызывается один раз ... – doctorlove

+1

@doctorlove Но иногда выполнение вычислений в конструкторе просто не подходит. –

ответ

0

Я предполагаю, что вы всегда можете создать наивное решение, как (более алгоритм, чем C++ кода):

class foo { 
    static std::vector<foo*> callers; 

    void Once() 
    { 
     if(std::find(callers.begin(), callers.end(), this)!=callers.end()) 
     { 
      // do stuff ... 
      callers.push_back(this); 
     } 
    } 

очевидно , вы можете иметь std::map<foo*, int>, если вам нужно будет контролировать, сколько раз вы хотите, чтобы вызывающие вызывали и изменяли код соответствующим образом.

Адресация «звонок не был сделан, а затем вызов перед уничтожением» (не уверен, правильно ли я его понял) вы можете просто вызвать Once() из деструктора, это решит проблему.

И, конечно же, возникает другая проблема: что, если ОС думает, что это мудрое решение создать элемент в том же месте, что и уже удаленный ... Чтобы избежать этого, в конце деструктора , или в начале конструктора вы должны удалить this со статического вектора/карты.

+1

Это ужасно неэффективно. Это O (n^2), где n - число объектов 'foo', которые являются живыми. Представьте, что у вас есть миллион объектов 'foo'; вы действительно хотите искать весь список каждый раз два раза для каждого объекта (один раз внутри 'Once()' и один раз внутри деструктора 'foo', чтобы удалить его из списка снова). – cmaster

+0

O можно улучшить, просто используя набор (или упорядоченный векторный и двоичный поиск для вставки и ... поиск), было очень оригинально реализовать такое решение «logging»/'check in-check out' в этом контекст. К сожалению, мой +1 проиграл из-за downvotes –

+0

@cmaster, это правда, однако этот подход позволит вам позже отделить проверку от самого класса и создать более общий обработчик, который сможет принимать все виды объектов для всех не нужно включать эту информацию в сам класс. – fritzone

1

Есть ли идиома или шаблон дизайна, связанный с этим?

class Foo { 
    bool once_called; 
    public: 
    void once() 
    { 
     if (once_called) 
      throw TwiceError(); 
     once_called = true; 
    } 
}; 

есть уязвимости рассмотреть?

Существует потенциальное состояние гонки, в котором два потока пытаются вызвать once на одном и том же объекте одновременно.

Есть ли решение, что весы красиво произвольного числа выполнений

счетчиком, изначально n. Уменьшите счетчик в функции-члене и вызовите сбой, когда счетчик достигнет нуля.

2

Afaik, для этого нет готового решения. Однако, достаточно просто написать его самостоятельно. Вот моменты, которые необходимо учитывать:

  1. Флаг/счетчик в порядке. Однако, если у вас есть параллельный доступ к вашему объекту, вы должны использовать флаг/счетчик типа std::atomic<>. Не используйте замок, это полный перебор.

  2. Вам необходимо проверить флаг/счетчик в функции, как описано.

  3. Вам также необходимо вставить вызов функции в свой деструктор. Как со всеми деструкторами, он должен быть виртуальным. Однако имейте в виду, что если объект относится к некоторому подклассу вашего класса, все деструкторы подкласса будут запущены до того, как ваш деструктор получит возможность вызвать Once(). Таким образом, вы можете вызвать Once() на дисфункциональный объект.

  4. Если вы должны убедиться, что Once() запускается перед возможными подклассами подорванные, вы должны переместить вызов из деструктора в finalize() метод, который устанавливает другой флаг, и утверждать на этом флаге в вашем деструкторе.

+0

Это было очень тщательное, thnx! –

1

Ну, подход счетчика - лучший способ, о котором я могу думать. Вы должны просто убедиться, что вы правильно масштабируете его в многопоточном сценарии, т. Е. для защиты счетчика от гонок данных:

#include<mutex> 

class foo { 
public: 
    foo() : counter (0) {} 

    const static int CALL_LIMIT = 1; 

    void Once() 
    { 
     mtx.lock(); 
     counter++; 
     if (counter>CALL_LIMIT) 
     { 
      mtx.unlock(); 
      return; /* or throw an error */ 
     } 
     mtx.unlock(); 
     /* Rest of the code */ 
    } 

private: 
    static std::mutex mtx; 
    int counter; 

}; 

И вы, конечно, следует рассмотреть вопрос о вызове Once() в деструкторе, а также.

+1

Ваше использование мьютекса неверно, оно остается заблокированным, когда вы делаете раннее возвращение! Вы можете легко избежать таких ошибок либо с помощью 'std :: lock_guard', либо типов без блокировки (' std :: atomic <> '). – cmaster

+0

@cmaster Спасибо, я также сначала рассмотрел использование незакрепленных типов, но тогда есть ли атомный тест и задание операции в STL для ints (есть один для boolean, но я не знаю TAS для других типов)? – Lazarus

+0

Вы можете просто использовать оператор инкремента, как в 'if (++ counter> CALL_LIMIT)', он гарантированно будет атомарным, если 'counter' является' atomic <> ', и он абсолютно эквивалентен вашему коду. – cmaster

Смежные вопросы