Что-то вроде вложенного мьютекса, но более общего?

#c #boost #stl

#c #повышение #stl

Вопрос:

Я работаю над проектом, в котором файл должен быть сохранен после выполнения некоторых операций с объектами-членами класса. Иногда мы хотим сохранить файл после одной операции, иногда нам нужно не сохранять его до тех пор, пока не будет выполнен пакет операций.

Моя идея состоит в том, чтобы использовать класс, который в основном работает как рекурсивный мьютекс. За исключением того, что вместо блокировки и разблокировки мьютекса я хочу, чтобы класс вызывал метод (в данном случае, сохранял файл), когда последний экземпляр класса в стеке выпадает из области видимости.

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

Ниже приведена простая реализация того типа поведения, который я ищу. Он будет печатать только «Привет, мир!» дважды, даже если DoItOnce() вызывается 11 раз. Я хотел бы использовать что-то вроде GenericGuard , извлекая его из признанного стандарта, а не вставляя свою собственную реализацию в базу кода. Возможно ли это?

 #include <iostream>

void Noop (void) { }
void HelloWorld (void) { std::cout << "Hello World!" << std::endl; }

// This is what I imagine a generic implementation would look like...
template <void (*InitFunc)(), void (*DestructFunc)()>
class GenericGuard
{
  int amp; _i;
  public:
  GenericGuard (int amp; i) : _i(i) { if (_i   == 0) { InitFunc(); } }
  ~GenericGuard () { if (--_i == 0) { DestructFunc(); } }
};

int HelloWorldCounter; // Use a factory class in real-world?

typedef GenericGuard<Noop, HelloWorld> HelloWorldGuard;

void DoSomethingOnce (void) 
{
  HelloWorldGuard G (HelloWorldCounter);
  // Do something
}

void DoItTenTimes (void)
{
  HelloWorldGuard G (HelloWorldCounter);
  for (int i = 0; i < 10;   i)
  {
    DoSomethingOnce();
  }
}

int main (void)
{
  DoSomethingOnce();

  DoItTenTimes();

  return 0;
}
  

Ответ №1:

Вы можете использовать a shared_ptr с пользовательской функцией удаления.

Пример :

 #include <memory>
#include <iostream>

void    HelloWorld(void *) { std::cout << "Hello World!" << std::endl; }

class   factory
{
public:
  static std::shared_ptr<void> get_instance()
  {
    static std::weak_ptr<void>   ref;
    if (ref.expired())
      {
        std::shared_ptr<void>    sp{nullptr, HelloWorld};
        ref = sp;
        return sp;
      }
    return ref.lock();
  }

};

void DoSomethingOnce (void)
{
  std::shared_ptr<void>  G = factory::get_instance();
  // Do something
}

void DoItTenTimes (void)
{
  std::shared_ptr<void>  G = factory::get_instance();
  for (int i = 0; i < 10;   i)
    {
      DoSomethingOnce();
    }
}

int     main(void)
{
  DoSomethingOnce();

  DoItTenTimes();

  return 0;
}
  

Комментарии:

1. Не могли бы вы представить пример того, как реализовать DoSomethingOnce и DoItTenTimes с shared_ptr помощью s? Я знаком с идеей пользовательских удалителей для интеллектуальных указателей, но я не вижу, как я могу использовать их для решения этой проблемы.

2. Похоже, вы ответили техническими средствами, а не подходом. Прочитав вопрос, я не думаю, что это то, о чем был вопрос (и комментарий подтверждает это)

3. @sehe Ну, этот ответ в основном посвящен следующей части вопроса: реализация класса, который это делает, не является проблемой, но это похоже на общую проблему, которую я просто не могу найти в Boost или STL. Существует ли уже существующее стандартное решение этой проблемы, или мне нужно создать свой собственный класс для этого?

4. Честно говоря, настоящая техника здесь — RAII, и shared_ptr это лишь случайное средство для нее. BOOST_SCOPE_EXIT было бы концептуально чище (поскольку нет указателя и выделения, нет необходимости в пересчете потоков или слабых указателях). И многие другие эквивалентные помощники могут быть использованы / созданы. Реальный вопрос, IMO, касается семантики (как сгруппировать работу).

5. @sehe Вы правы, подсчет ссылок — это концепция, основанная на RAII, поэтому техника действительно RAII, но я думаю, что это нечто большее, чем просто подсчет ссылок IMO сам по себе является способом семантической группировки работы, уверен, что он очень легкий, без сильного семантического ожидания, говорящего «эйэти экземпляры связаны друг с другом, и я хочу знать, когда все они будут выполнены «, и вы, вероятно, могли бы создать что-то гораздо более явное, но этого может быть более чем достаточно в некоторых случаях, когда определение / применение структурного шаблона может быть излишним, если вам не нужно гораздо больше возможностей.

Ответ №2:

Шаблон, который вам нужен, кажется, действительно хорошо известен: вы хотите группировать операции в транзакциях [1].

Связанные шаблоны, которые приходят мне на ум, следующие

  • Шаблон команды (с известным примером выполнения / отмены)

    • Составной шаблон (чтобы у вас была команда, которая объединяет несколько других команд, формируя дерево);
  • Шаблон единицы работы; это позволяет группировать ожидающие изменения и применять их как группу

  • Программная транзакционная память (которая фокусируется на реальной атомарности операций, например, с безопасностью исключений).

Нет, я не ярый сторонник шаблонов, но мне нравятся концепции, которые он дает вам для обсуждения вещей: Итак, то, что вам действительно нужно, — это единица работы (которая может быть такой же простой, как сгруппированные команды), и вам нужна «Транзакция», которая автоматически применяет изменения при уничтожении.


В зависимости от вашего реального приложения, возможно, было бы неплохо придерживаться подхода к изменяемым объектам, который у вас, похоже, есть сейчас, и просто время от времени сериализовать его. Если домен приложения становится немного более интересным (например, с потоковой обработкой, отменой и / или управлением версиями?) вы быстро обнаружите, что жизнь становится намного проще, когда вы переходите к модели документа, которая представляет собой граф ссылок на неизменяемые узлы. Это позволяет вам дешево «редактировать» сложный объектный граф, просто заменяя узлы. Неизменяемые узлы обеспечивают их безопасное совместное использование даже в многопоточных средах.

Я думаю, что с вашим вопросом связан разговор Шона Парента о приправах C . Хотя это фокусируется на том, как организовать вашу модель документа, я чувствую, что это может быть довольно проницательным и может дать вам «прорывное» изменение точки зрения, чтобы снова упростить проблему.


[1] которые вообще не должны быть атомарными в этом контексте, хотя они могут (должны) находиться в домене вашего приложения.