вторник, 27 октября 2009 г.

[comp.prog.cpp] Очередной маленький C++ велосипедик: mutable_copy_on_demand

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

void process_object( const some_object_t & original )
  {
    // Делаем копию, которая может модифицироваться.
    some_object_t copy( original );
    // На последующую обработку уходит уже копия объекта.
    do_first_step_then_others( copy );
  }

void do_first_step_then_other( some_object_t & obj )
  {
    // Обработка заключается в замене одного из полей,
    // если его текущее значение не удовлетворяет
    // некоторым условиям.
    if( need_change_field_1( obj ) )
      obj.set_field_1( value_for_field_1() );

    do_second_step_then_others( obj );
  }
...
void do_N_step_then_send( some_object_t & obj )
  {
    if( need_change_field_N( obj ) )
      obj.set_field_N( value_for_field_N() );

    send( obj );
  }

При всей своей простоте это решение мне не нравилось тем, что копия объекта создается всегда. Даже если в 90% случаев объект вообще не модифицировался. А исходный объект был не очень маленьким – с десятком строковых полей + еще несколькими полями со сложной структурой.

Т.е. задача была в том, чтобы создавать копию объекта только при первой модификации. Но, если модификация произошла, чтобы именно копия объекта рассматривалась как актуальное значение. Для этого я написал очень простой класс (его шаблонный вариант приведен здесь). С его использованием преобразование объекта стало выглядеть приблизительно так:

void process_object( const some_object_t & original )
  {
    // Делаем "ленивую" копию, которая может модифицироваться.
    mutable_copy_on_demand_t< some_object_t > copy( original );
    // На последующую обработку уходит уже копия объекта.
    do_first_step_then_others( copy );
  }

void do_first_step_then_others(
  mutable_copy_on_demand_t< some_object_t > & obj )
  {
    // Обработка заключается в замене одного из полей,
    // если его текущее значение не удовлетворяет
    // некоторым условиям.
    if( need_change_field_1( obj.immutable() ) )
      obj.changeable().set_field_1( value_for_field_1() );

    do_second_step_then_others( obj );
  }
...
void do_N_step_then_send(
  mutable_copy_on_demand_t< some_object_t > & obj )
  {
    if( need_change_field_N( obj.immutable() ) )
      obj.changeable().set_field_N( value_for_field_N() );

    send( obj.immutable() );
  }

Объект mutable_copy_on_demand_t хранит ссылку на исходный объект и указатель на копию. Изначально копии нет и метод immutable возвращает ссылку на исходную копию. При первом обращении к методу changeable создается копия, и метод immutable начинает возвращать ссылку на нее.

Вот такой частный случай Copy-On-Write, упрощенный за счет того, что ссылку на исходный объект не нужно контролировать – она в данном сценарии гарантированно остается неизменной.

2 комментария:

Dmitry Vyukov комментирует...

Re: А исходный объект был не очень маленьким – с десятком строковых полей + еще несколькими полями со сложной структурой.

SMS? :)

eao197 комментирует...

SMS? ;)

Ага. Но не сам по себе, а с туевой хучей сопроводительной информации.