1 апр. 2009 г.

Использование boos::call_traits

Все содержимое <boost/call_traits.hpp> объявлено в пространстве имен boost.

Шаблонный класс call_traits<T> инкапсулирует "наилучший" метод передачи параметра определенного типа T в или из функции, и состоит из собрания операторов typedef, определенных как показано в нижеследующей таблице. Предназначение call_traits заключается в создании гарантий, что проблемы типа "ссылка на ссылку" не возникнут, и что параметры передаются наиболее эффективным допустимым способом (см. примеры).

Заметьте, что для компиляторов, которые не поддерживают частичную специализацию или шаблоны-члены, использование шаблона call_traits не принесет никаких выгод. Дополнительно к этому, если компилятор поддерживает шаблоны-члены и не поддерживает частичную специализацию (Visual C++ 6), тогда call_traits не может быть использован с типами массивов (хотя он может быть использован для решения проблемы ссылка на ссылку).

существующая практика

эквивалент с call_traits

описание

замечания

T
(возвращается сама величина)

call_traits<T>::value_type

Определяет тип, который представляет "значение" типа T. Используйте для функций, которые возвращают значение, а также для хранения величин типа T.

2

T&
(возврат по значению)

call_traits<T>::reference

Определяет тип, представляющий ссылку на тип  T. Используйте для функций, которые должны возвращать T&.

1

const T&
(возврат по значению)

call_traits<T>::const_reference

Определяет тип, представляющий константную ссылку на тип T. Используйте для функций, которые должны возвращать const T&.

1

const T&
(параметр функции)

call_traits<T>::param_type

Определяет тип, который представляет "наилучший" способ передавать параметр типа T в функцию.

1,333

Внимание:

Если T уже является типом ссылки, тогда call_traits определяется таким способом, что проблема ссылка на ссылку не возникает (требуется поддержка компилятором частичной специализации).

Если T является типом массива, тогда call_traits определяет value_type как "константный указатель на тип" вместо "массива типов" (требуется частичная специализация). Обратите внимание, что если Вы используете value_type для хранения значения, то это приведет к хранению "константного указателя на массив" вместо хранения самого массива. Это может быть хорошим или плохим решением в зависимости от конкретной ситуации.

Если T это небольшой по размеру встроенный тип или указатель, тогда param_type определяется как T const, вместо T const&. Это может улучшить шансы компилятора на оптимизацию циклов в теле функции, если они зависят от передаваемого параметра. В остальных случаях сематника передаваемого параметра остается неизменной. Для реализации этой возможности необходима частичная специализация.

 

Возможность создания копированием

Следующая таблица определяет, какие типы в шаблоне call_traits всегда могут создаваться копированием из других типов. Случаи, отмеченные '?', справедливы только если T можно конструировать копированием:

 

В:

Из:

T

value_type

reference

const_reference

param_type

T

?

?

Y

Y

Y

value_type

?

?

N

N

Y

reference

?

?

Y

Y

Y

const_reference

?

N

N

Y

Y

param_type

?

?

N

N

Y

 

Если T тип с работающим присваиванием, то следующие виды присваивания возможны:

 

В:

Из:

T

value_type

reference

const_reference

param_type

T

Y

Y

-

-

-

value_type

Y

Y

--

-

-

reference

Y

Y

-

-

-

const_reference

Y

Y

--

-

-

param_type

Y

Y

--

-

-

 

Примеры

Следующая таблица показывает эффект, который получается применением call_traits к различным типам. Подразумевается, что компилятор поддерживает частичную специализацию: если это не так, то все типы ведут себя так же, как в графе для "myclass", и call_traits не может быть использован со ссылками или типами массивов.

 

тип-результат call_traits:

исходный тип T

value_type

reference

const_reference

param_type

может применяться для:

myclass

myclass

myclass&

const myclass&

myclass const&

всех определенных пользователем типов.

int

int

int&

const int&

int const

всех небольших встроенных типов.

int*

int*

int*&

int*const&

int* const

любых указателей.

int&

int&

int&

const int&

int&

любых ссылок.

const int&

const int&

const int&

const int&

const int&

всех константных ссылок.

int[3]

const int*

int(&)[3]

const int(&)[3]

const int* const

всех типов маккисов.

const int[3]

const int*

const int(&)[3]

const int(&)[3]

const int* const

всех константных массивов.

 

Пример 1:

Следующий класс является простейшим случаем, он хранит величину типа T (см. файл call_traits_test.cpp). Демонстрируется, как можно использовать все возможные типы в call_traits:

template <class T>
struct contained
{
   // define our typedefs first, arrays are stored by value
   // so value_type is not the same as result_type:
   typedef typename boost::call_traits<T>::param_type       param_type;
   typedef typename boost::call_traits<T>::reference        reference;
   typedef typename boost::call_traits<T>::const_reference  const_reference;
   typedef T                                                value_type;
   typedef typename boost::call_traits<T>::value_type       result_type;

   // stored value:
   value_type v_;
   
   // constructors:
   contained() {}
   contained(param_type p) : v_(p){}
   // return byval:
   result_type value() { return v_; }
   // return by_ref:
   reference get() { return v_; }
   const_reference const_get()const { return v_; }
   // pass value:
   void call(param_type p){}

};

Пример 2 (проблема ссылки на ссылку):

Рассмотрим определение std::binder1st:

template <class Operation> 
class binder1st : 
   public unary_function<typename Operation::second_argument_type, typename Operation::result_type> 
{ 
protected: 
   Operation op; 
   typename Operation::first_argument_type value; 
public: 
   binder1st(const Operation& x, const typename Operation::first_argument_type& y); 
   typename Operation::result_type operator()(const typename Operation::second_argument_type& x) const; 
}; 

Теперь посмотрим, что будет в относительно общем случае, когда функтор получает свой второй аргумент как ссылку, что подразумевает, что Operation::second_argument_type является типом ссылки, operator() теперь определяется как получающий ссылку на ссылку, что некорректно для текущего стандарта C++. Решение заключается в том, чтобы модифицировать определение operator() с использованием call_traits:

typename Operation::result_type operator()(typename call_traits<typename Operation::second_argument_type>::param_type x) const;

Теперь в случае, если Operation::second_argument_type является типом ссылки, аргумент передается как ссылка, и проблема "ссылка на ссылку" не возникает.

Пример 3 (проблема с make_pair):

Если мы передает имя массива как один (или оба) аргумента в std::make_pair, тогда алгоритм вывода аргументов шаблона воспринимает преданный параметр как "константная ссылка на массив типов T", это также применимо к строковым литералам (которые на самом деле также являются массивами). Следовательно вместо возврата пары указателей, происходит возврат пары массивов, а так как тип массивов не может создаваться копированием, то код не компилируется. Одним решением может быть явное преобразование аргументов make_pair к указателям, но call_traits дает лучшее (т.е. автоматическое) решение (которое безопасно в обобщенном коде, где преобразования могут привести к неверному результату):

template <class T1, class T2>
std::pair<
   typename boost::call_traits<T1>::value_type, 
   typename boost::call_traits<T2>::value_type> 
      make_pair(const T1& t1, const T2& t2)
{
   return std::pair<

      typename boost::call_traits<T1>::value_type, 
      typename boost::call_traits<T2>::value_type>(t1, t2);
}

Здесь полученные при выводе типы аргументов будут автоматически усечены до указателей, если выведенные типы являются массивами. Аналогичная ситуация возникает со стандартными байндерами и адаптерами (binders and adapters): в принципе, это справедливо для любой функции, которая "обертывает" временный объект, чей тип выводится. Обратите внимание, аргументы функции для make_pair не выражаются в терминах call_traits: это могло бы привести к невозможности вывести аргументы шаблона.

Пример 4 (оптимизация алгоритма fill):

Шаблон call_traits может "оптимизировать" передачу небольших встроенных встроенных типов как аргументов функции. В следующем примере (см. fill_example.cpp), версия std::fill оптимизируется двумя способами: если передаваемый тип является встроенным однобайтовым типом, то используется std::memset, иначе используется обычная реализация алгоритма копирования, но с применением "оптимизации" передачи параметров через call_traits:

namespace detail{

template <bool opt>
struct filler
{{
   template <typename I, typename T>

   static void do_fill(I first, I last, typename boost::call_traits<T>::param_type val);
   {
      while(first != last)
      {
         *first = val;
         ++first;
      }
   }
};

template <>
struct filler<true>
{
   template <typename I, typename T>
   static void do_fill(I first, I last, T val)
   {
      memset(first, val, last-first);
   }
};

}

template <class I, class T>
inline void fill(I first, I last, const T& val)
{
   enum{ can_opt = boost::is_pointer<I>::value
                   && boost::is_arithmetic<T>::value
                   && (sizeof(T) == 1) };
   typedef detail::filler<can_opt> filler_t;
   filler_t::template do_fill<I,T>(first, last, val);}}

Примечение: причина того, что "оптимальным" небольших встроенных типов является передача как T const вместо const T& заключается в том, что компилятор может определить, что величина неизменна и для нее нет синонимов (aliases). С такой информацией компилятор способен закэшировать передаваемое значение в регистре, развернуть (unroll) цикл, или использовать параллельные инструкции. Точнее говоря, эффект от применения такой оптимизации зависит от компилятора.

Обратите внимание, аргументы функции для make_pair не выражаются в терминах call_traits: это могло бы привести к невозможности вывести аргументы шаблона. Вместо этого fill работает как "тонкая обертка", предназначенная для выполнения вывода аргументов шаблона. В любом случае компилятор соптимизирует вызов fill, заменив его на вызов filler<>::do_fill, который использует call_traits.

Обоснование

Следующие замечанию кратко описывают причины выбора решений в call_traits.

Все определенные пользователем типы следуют "существующей практике" и не нуждаются в комментариях.

Небольшие встроенные типы (то, что стандарт называет фундаментальными типами [3.9.1]) отличаются от существующей практики только реализацией call_traits<>::param_type. В этом случае передача "T const" совместима с существующей практикой, но может улучшить эффективность в некоторых случаях (см. Пример 4), в любом случае это не должно быть хуже, чем существующая практика.

Указатели ведут себя как небольшие встроенные типы.

Для ссылочных типов реализация следует примеру 2 - ссылки на ссылки не допускаются, так что call_traits предотвращает возникновение этой проблемы. Есть предложение изменить C++ таким образом, чтобы ссылка на ссылку была эквивалентна одной ссылке (предложение #106, сделано Bjarne Stroustrup), call_traits<T>::value_type и call_traits<T>::param_type обеспечивают этот же предложенный эффект, без необходимости менять язык.

Для типов массивов, функция, которая получает массив как аргумент, усекает его до указателя: это ознчает, что тип действительного параметра отличается от объявленного типа. Иногда это может вызвать проблемы в коде шаблона, который опирается на объявленный тип параметра. К примеру:

template <class T>
struct A
{
   void foo(T t);
};

В данном случае если мы конкретизируем A<int[2]>, тогда объявленный тип параметра, передаваемого в функцию-метод foo будет int[2], но действительный тип будет const int*. Если мы попытаемся использовать тип T внутри тела функции, тогда есть большая вероятность, что наш код не скомпилируется:

template <class T>

void A<T>::foo(T t)
{
   T dup(t); // doesn't compile for case that T is an array.
}

При использовании call_traits усечение массива к указателю является явным, и тип параметра тот же самый, что объявленный тип:

template <class T>
struct A
{
   void foo(typename call_traits<T>::value_type t);
};

template <class T>

void A<T>::foo(typename call_traits<T>::value_type t)
{
   typename call_traits<T>::value_type dup(t); // OK even if T is an array type.
}

Для value_type (возврат по значению) опять-таки только массив может быть возвращен, а не целый массив, и снова call_traits делает усечени явным. Член value_type полезен в любом случае, когда массив должен быть явно усечен до указателя - пример 3 показывает тестовый случай (примечание: специализация с массивом для call_traits наименее понятна из всех специализаций call_traits; если данная семантика вызывает у Вас какие-то трудности, или не решает какую-то проблему с массивами, тогда я буду рад услышать об этом. Большинству разработчиков данная специализация не нужна).

P.S.: Нагло спёрто отсюда: BOOST C++: заголовочный файл <boost/call_traits.hpp>

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

Отправить комментарий