![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
Call TraitsBoost , ,
|
Existing practice |
call_traits equivalent |
Description |
Notes |
T |
|
Defines a type that represents the "value" of type T. Use this for functions that return by value, or possibly for stored values of type T. | 2 |
T& |
|
Defines a type that represents a reference to type T. Use for functions that would normally return a T&. | 1 |
const
T& |
|
Defines a type that represents a constant reference to type T. Use for functions that would normally return a const T&. | 1 |
const
T& |
|
Defines a type that represents the "best" way to pass a parameter of type T to a function. | 1,3 |
Примечания:
value_type
как "постоянный указатель на тип", а не "массив типа" (требует частичной специализации). Обратите внимание, что если вы используете value_type в качестве сохраненного значения, это приведет к хранению постоянного указателя на массив, а не на сам массив. Это может быть или не быть хорошей вещью в зависимости от того, что вам действительно нужно (другими словами, позаботьтесь!).param_type
определяется какT const
, а неT
const&
. Это может улучшить способность компилятора оптимизировать петли в теле функции, если они зависят от пройденного параметра, семантика пройденного параметра в остальном неизменна (требуется частичная специализация).
Следующая таблица определяет, какие типы call_traits всегда можно скопировать, из каких других типов эти записи, помеченные буквой «?», верны только тогда и только тогда, когда T является копируемым:
To: |
|||||
From: | 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 является присваиваемым типом, возможны следующие назначения:
To: |
|||||
From: | 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 type: |
|||||
Original type T |
value_type |
reference |
const_reference |
param_type |
Applies to: |
myclass |
myclass |
myclass& |
const myclass& |
myclass const& |
All user defined types. |
int |
int |
int& |
const int& |
int const |
All small built-in types. |
int* |
int* |
int*& |
int*const& |
int* const |
All pointer types. |
int& |
int& |
int& |
const int& |
int& |
All reference types. |
const int& |
const int& |
const int& |
const int& |
const int& |
All constant-references. |
int[3] |
const int* |
int(&)[3] |
const int(&)[3] |
const int* const |
All array types. |
const int[3] |
const int* |
const int(&)[3] |
const int(&)[3] |
const int* const |
All constant-array types. |
Следующий класс является тривиальным классом, который хранит некоторый тип 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){} };
Рассмотрим определение 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; };
Теперь рассмотрим, что происходит в относительно распространенном случае, когда функтор принимает свой второй аргумент в качестве ссылки, что подразумевает, что Операция::second_argument_type
является эталонным типом, оператор()
теперь в конечном итоге будет принимать ссылку на ссылку в качестве аргумента, и это в настоящее время не является законным. Решение здесь состоит в том, чтобы изменить оператор()
, чтобы использовать Call_traits:
typename Operation::result_type operator()(typename call_traits<typename Operation::second_argument_type>::param_type x) const;
Теперь в случае, когда Операция::second_argument_type
является эталонным типом, аргумент передается в качестве ссылки, и возникает ссылка на ссылку.
Если мы передаем имя массива как один (или оба) аргумента std::make_pair
, то дедукция аргумента шаблона выводит переданный параметр как "const ссылка на массив T", это также относится к строковым буквам (которые на самом деле являются буквальными массивами). Следовательно, вместо того, чтобы возвращать пару указателей, он пытается вернуть пару массивов, и поскольку тип массива не является копируемым, код не компилируется. Одно из решений состоит в том, чтобы явно отбрасывать аргументы, чтобы сделать парой указатели, но 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); }
Здесь выведенные типы аргументов будут автоматически деградировать до указателей, если выведенные типы являются массивами, аналогичные ситуации происходят в стандартных связующих и адаптеров: в принципе, в любой функции, что "wraps" временной, чей тип выводится. Обратите внимание, что аргументы функции make_pair не выражены в терминах call_traits: это предотвратило бы дедукцию аргумента шаблона от функционирования.
Шаблон call_traits будет "оптимизировать" прохождение небольшого встроенного типа в качестве функционального параметра, это в основном имеет эффект, когда параметр используется в корпусе петли. В следующем примере (см. fill_example.cpp) версия std::fill оптимизируется двумя способами: если пройденный тип представляет собой один байт встроенного типа, то для выполнения заполнения используется std::memset, в противном случае используется обычная реализация C++, но с пройденным параметром "optimized" с использованием 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&", компилятор может сказать и то, что значение является постоянным и что оно свободно от псевдонимов. С помощью этой информации компилятор может кэшировать пройденное значение в регистре, разворачивать цикл или использовать явно параллельные инструкции: если какая-либо из них поддерживается. Точный пробег, который вы получите от этого, зависит от вашего компилятора - мы действительно можем использовать какое-то точное программное обеспечение для бенчмаркинга.
Обратите внимание, что аргументы функции для заполнения не выражаются в терминах call_traits: это предотвратило бы дедукцию аргумента шаблона от функционирования. Вместо того, чтобы заполнять действия в качестве "тонкой обертки", которая существует для выполнения вычета аргументов шаблона, компилятор оптимизирует вызов, чтобы заполнить все вместе, заменяя его призывом к заполнению <>::do_fill, который использует Call_traits.
Следующие примечания предназначены для краткого описания рациональных решений, сделанных в Call_traits.
Все определяемые пользователем типы следуют существующей практике и не нуждаются в комментариях.
Небольшие встроенные типы (то, что стандарт называет фундаментальными типами [3.9.1]) отличаются от существующей практики только в param_type typedef. В этом случае прохождение "T const" совместимо с существующей практикой, но в некоторых случаях может улучшить производительность (см. Пример 4), в любом случае это никогда не должно быть хуже, чем существующая практика.
Указатели следуют той же рациональности, что и небольшие встроенные типы.
Для типов ссылок рациональное Пример 2 - ссылки на ссылки не допускаются, поэтому члены call_traits должны быть определены так, чтобы эти проблемы не возникали. Существует предложение изменить язык таким образом, чтобы "ссылка на ссылку была ссылкой" (выпуск No 106, представленный Бьярном Страуструпом), call_traits
Для типов массивов функция, которая принимает массив в качестве аргумента, деградирует тип массива до типа указателя: это означает, что тип фактического параметра отличается от его заявленного типа, что может вызвать бесконечные проблемы в коде шаблона, который полагается на объявленный тип параметра. Например:
template <class T> struct A { void foo(T t); };
В этом случае, если мы инстанцируем A
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, если данная семантика вызывает для вас конкретные проблемы или не решает конкретную проблему, связанную с массивом, тогда мне было бы интересно услышать об этом). Большинство людей, вероятно, никогда не будут использовать эту специализацию.
Пересмотрено 01 сентября 2000 года
Авторское право 2000 Стив Клири, Беман Доуз, Говард Хиннант и Джон Мэддок. Использование, модификация и распространение регулируются Лицензией на программное обеспечение Boost версии 1.0. (См. сопроводительный файл LICENSE_1_0.txt или копию по адресу ) http://www.boost.org/LICENSE_1_0.txt .
Статья Call Traits раздела может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: ::
реклама |