eao197 on the Web
Сайт Евгения Охотникова
[ Главная | Проекты | Описания | Об авторе | Лицензия ]

C++ tricks: mutable_copy_on_demand

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

void modify_and_send()
   {
      some_object_t obj;
      if( need_change_field_1( obj ) )
         obj.set_field_1( value_for_field_1() );
      if( need_change_field_2( obj ) )
         obj.set_field_2( value_for_field_2() );
      ...
      if( need_change_field_N( obj ) )
         obj.set_field_N( value_for_field_N() );

      send( obj );
   }

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

void modify_and_send( const some_object_t & original )
   {
      some_object_t obj( original );
      if( need_change_field_1( obj ) )
         obj.set_field_1( value_for_field_1() );
      if( need_change_field_2( obj ) )
         obj.set_field_2( value_for_field_2() );
      ...
      if( need_change_field_N( obj ) )
         obj.set_field_N( value_for_field_N() );

      send( obj );
   }

Но что, если в большинстве случаев объект вообще не будет модифицироваться? Мы будем делать копию просто так, на всякий случай. Для сложных объектов это может стать серьезным расходом ресурсов. Значит, копию нужно делать только при возникновении необходимости в первой модификации. Например, вот так:

void duplicate_if_needed(
   std::auto_ptr< some_object_t > copy,
   const some_object_t & original )
   {
      if( !copy.get() )
         copy.reset( new some_object_t( original ) );
   }

const some_obj_t & safe_reference(
   std::auto_ptr< some_object_t > copy,
   const some_object_t & original )
   {
      if( copy.get() )
         return *copy;
      else
         return original;
   }

void modify_and_send( const some_object_t & original )
   {
      std::auto_ptr< some_object_t > copy;
      if( need_change_field_1( safe_reference( copy, original ) ) )
         {
            duplicate_if_needed( copy, original );
            copy->set_field_1( value_for_field_1() );
         }
      if( need_change_field_2( safe_reference( copy, original ) ) )
         {
            duplicate_if_needed( copy, original );
            copy->set_field_2( value_for_field_2() );
         }
      ...
      if( need_change_field_N( safe_reference( copy, original ) ) )
         {
            duplicate_if_needed( copy, original );
            copy->set_field_N( value_for_field_N() );
         }

      send( safe_reference( copy, original ) );
   }

Уже получается не красиво. Еще хуже, если modify_and_send реализована как цепочка вложенных вызовов. Вроде такого:

void modify_and_send( const some_object_t & original )
   {
      if( need_change_field_1( ... ) )
         ... // Изменение field_1

      change_field_2_and_send( ... );
   }

void change_field_2_and_send( ... )
   {
      if( need_change_field_2( ... ) )
         ... // Изменение field_2

      change_field_3_and_send( ... );
   }
...
void change_field_N_and_send( ... )
   {
      if( need_change_field_N( ... ) )
         ... // Изменение field_N

      send( ... );
   }

Вопрос в том, что должно стоять на месте этих многоточий?

Эта ситуация может быть разрешена с помощью следующего шаблона:

template< class T >
class mutable_copy_on_demand_t
   :  private noncopyable
   {
   public :
      // Единственный конструктор получает ссылку на исходное значение.
      mutable_copy_on_demand_t( const T & original )
         :  m_original( original )
         {}

      // Доступ к объекту в режиме только чтения.
      // Если не было ни одной попытки модификации объекта, то
      // возвращается исходное значение. Если объект хотя бы
      // один раз модифицировался, то возвращается его изменяемая
      // копия.
      const T & immutable() const
         {
            if( m_mutable.get() )
               return *m_mutable;
            else
               return m_original;
         }

      // Доступ к объекту в режиме записи.
      // Если это первое подобное обращение, то создается копия
      // исходного объекта. Если же копия уже создана, то ссылка
      // на нее возвращается без создания новой копии.
      T & changeable()
         {
            if( !m_mutable.get() )
               m_mutable.reset( new T( m_original ) );
            return *m_mutable;
         }

   private :
      const T & m_original;
      std::auto_ptr< T > m_mutable;
   };

С помощью такого шаблона модификация объекта записывается элементарно:

void modify_and_send( const some_object_t & original )
   {
      mutable_copy_on_demand_t< some_object_t > obj( original );
      if( need_change_field_1( obj.immutable() ) )
         obj.changeable().set_field_1( value_for_field_1() );

      change_field_2_and_send( obj );
   }

void change_field_2_and_send( mutable_copy_on_demand_t< some_object_t > & obj )
   {
      if( need_change_field_2( obj.immutable() ) )
         obj.changeable().set_field_2( value_for_field_2() );

      change_field_3_and_send( obj );
   }
...
void change_field_N_and_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() );
   }

© 2003-2009 Е.А. Охотников
$LastChangedDate: 2009-10-24 19:23:46 +0400 (РЎР±, 24 РѕРєС‚ 2009) $
e-mail

Hosted by uCoz