вторник, 2 марта 2010 г.

[prog.c++.bicycle] Еще один трюк в отсутствии rvalue references

В C++0x планируется добавление интересной штуки под названием rvalue references, которая должна убрать изрядную часть накладных расходов в C++ программах. Но пока C++0x не принят, приходится жить без rvalue references и изобретать разные вспомогательные велосипедики.

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

class demo_t
   {
   private :
      std::string m_destination;
      std::string m_source;
      std::list< std::string > m_hints;

   public :
      demo_t(
         const std::string & destination,
         const std::string & source,
         const std::list< std::string > & hints )
         :  m_destination( destination )
         ,  m_source( source )
         ,  m_hints( hints )
         {}
   ...
};

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

std::string destination = domain + "/" + subject + "/" + timestamp;
std::list< std::string > hints;
hints.push_back( "lifetime: default" );
hints.push_back( "priority: default" );

demo_t demo( destination, source, hints );

В этом случае временные объекты destination и hints после объявления demo не нужны. И было бы хорошо, если бы при инициализации demo их значения перешли бы объекту demo (та самая move semantic из C++0x).

Этого можно было бы достичь с помощью temporary_object_ref, если добавить в demo_t еще один конструктор:

class demo_t
   {
   ...
   public :
      ... // Остальные конструкторы.
      demo_t(
         temporary_object_ref_t< std::string > destination,
         const std::string & source,
         temporary_object_ref_t< std::list< std::string > > hints )
         :  m_source( source )
         {
            destination.writeable().swap( m_destination );
            hints.writeable().swap( m_hints );
         }
   ...
};

...

demo_t demo(
      make_temporary_object_ref( destination ),
      source,
      make_temporary_object_ref( hints ) );

Но здесь уже видна проблема – нам придется писать столько вариантов конструктора demo_t, сколько возможных сочетаний временных и константных аргументов встретится в программе. Понятно, что это тупиковый путь.

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

class demo_t
   {
   ...
   public :
      demo_t(
         const_or_swappable_t< std::string > destination,
         const_or_swappable_t< std::string > source,
         const_or_swappable_t< std::list< std::string > > hints )
         {
            destination.apply_value( m_destination );
            source.apply_value( m_source );
            hints.apply_value( m_hints );
         }
   ...
};

...

demo_t demo(
      swappable_object( destination ),
      const_object( source ),
      swappable_object( hints ) );

Весь фокус в простом шаблоне const_or_swappable_t (код которого будет приведен ниже). Если объекту const_or_swappable_t передается константный аргумент, то в своем методе apply_value он выполняет его копирование. Если же аргументом является временный объект – то в apply_value вызывается swap. Тип аргумента (константный или временный) указывает программист посредством вспомогательных функций const_object и swappable_object.

Disclaimer. Пока это решение на практике я еще ни разу не применил. Самым явным его недостатком является то, что при его использовании сначала вызываются конструкторы по умолчанию для атрибутов класса, а только затем отрабатывают apply_value. Это так же лишние накладные расходы. А так же нельзя инициализировать подобным образом атрибуты-константы.

А вот код const_or_swappable_t с необходимыми вспомогательными функциями:

struct constant_object_tag {};
struct swappable_object_tag {};

template< class T >
class const_or_swappable_t
   {
      union {
         const T * const_ptr;
         T * swappable_ptr;
      } m_ptr;
      bool m_is_const;
   public :
      const_or_swappable_t( constant_object_tag, const T & o )
         :  m_is_const( true )
         {
            m_ptr.const_ptr = &o;
         }
      const_or_swappable_t( swappable_object_tag, T & o )
         :  m_is_const( false )
         {
            m_ptr.swappable_ptr = &o;
         }

      void apply_value( T & to ) const
         {
            if( m_is_const )
               to = *(m_ptr.const_ptr);
            else
               m_ptr.swappable_ptr->swap(to);
         }
   };

template< class T >
const_or_swappable_t< T > const_object( const T & o )
   {
      return const_or_swappable_t< T >( constant_object_tag(), o );
   }

template< class T >
const_or_swappable_t< T > swappable_object( T & o )
   {
      return const_or_swappable_t< T >( swappable_object_tag(), o );
   }

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