boost::reference_wrapper<T>
имеет конструктор Явный T&
, а std::reference_wrapper<T>
имеет неявный. Итак, в следующем коде:
foo = bar;
Если foo
является boost::reference_wrapper
, код не сможет скомпилироваться (что хорошо, поскольку reference_wrapper
имеет не ту же семантику фактической ссылки.
Если foo
является std::reference_wrapper
, код будет "переупорядочить" foo
ссылку на bar
(вместо назначения значения, как можно было бы ошибочно ожидать).
Это может привести к неуловимым ошибкам... Рассмотрим следующий пример:
В версии 1.0 некоторой гипотетической библиотеки:
void set_max(int& i, int a, int b) {
i = (a > b) ? a : b;
}
В новой версии (1.1) set_max
преобразуется в шаблон для приема целых чисел любой ширины (или UDT) без изменения интерфейса:
template<typename T1, typename T2, typename T3>
void set_max(T1& i, T2 a, T3 b) {
i = (a > b) ? a : b;
}
Затем, наконец, в некоторых приложениях с использованием библиотеки:
// i is a std::reference_wrapper<int> passed to this template function or class
set_max(i, 7, 11);
В этом примере библиотека меняет свою реализацию set_max
без изменения интерфейса вызова. Это будет бесшумно нарушать любой код, который передает ему std::reference_wrapper
, поскольку аргумент больше не будет преобразовываться в int&
и вместо этого "переподдерживается" на обвисшую ссылку (a
или b
).
Мой вопрос: Почему комитет по стандартам решил разрешить неявное преобразование (от T&
до std::reference_wrapper<T>
) вместо следующего boost
и сделать конструктор T&
явным?
Изменить: (в ответ на ответ Джонатана Вакли)...
Оригинальная демонстрация (в приведенном выше разделе) намеренно лаконична, чтобы показать, как тонкое изменение библиотеки может привести к использованию std::reference_wrapper
внесения ошибок в приложение.
Следующее демо предоставлено, чтобы показать реальное использование законного использования reference_wrapper
для "передачи ссылок через интерфейсы" в ответ на точку Джонатана Вакли.
- От разработчика/поставщика A
Нечто похожее на std::bind
, но притворяется, что оно специализировано для какой-то задачи:
template<typename FuncType, typename ArgType>
struct MyDeferredFunctionCall
{
MyDeferredFunctionCall(FuncType _f, ArgType _a) : f(_f), a(_a) {}
template<typename T>
void operator()(T t) { f(a, t); }
FuncType f;
ArgType a;
};
- От разработчика/поставщика B
A RunningMax
класс функтора. Между версиями 1.0 и 1.1 этой воображаемой библиотеки реализация RunningMax
была изменена на более общую, без изменения интерфейса вызова. Для целей этой демонстрации старая реализация определена в пространстве имен lib_v1
, а новая реализация в определении в lib_v2
:
namespace lib_v1 {
struct RunningMax {
void operator()(int& curMax, int newVal) {
if ( newVal > curMax ) { curMax = newVal; }
}
};
}
namespace lib_v2 {
struct RunningMax {
template<typename T1, typename T2>
void operator()(T1& curMax, T2 newVal) {
if ( newVal > curMax ) { curMax = newVal; }
}
};
}
- И последнее, но не менее важное: конечный пользователь всего кода:
Некоторые разработчики, использующие код от Vendor/Developer A и B, выполняют определенную задачу:
int main() {
int _i = 7;
auto i = std::ref(_i);
auto f = lib_v2::RunningMax{};
using MyDFC = MyDeferredFunctionCall<decltype(f), decltype(i)>;
MyDFC dfc = MyDFC(f, i);
dfc(11);
std::cout << "i=[" << _i << "]" << std::endl; // should be 11
}
Обратите внимание на следующее:
-
Конечный пользователь использует
std::reference_wrapper
способ, которым он предназначался. -
Индивидуально, ни один из кода не имеет ошибок или логических ошибок, и все отлично работает с исходной версией библиотеки поставщиков B.
-
boost:: reference_wrapper не сможет скомпилироваться после обновления библиотеки, в то время как std:: reference_wrapper тихо вводит ошибку, которая может быть или не быть поймана в регрессионных тестах.
-
Отслеживание такой ошибки было бы трудным, так как "переупорядочение" не является ошибкой памяти, которую могли бы использовать такие инструменты, как
valgrind
. Более того, фактический сайт неправильного использованияstd::reference_wrapper
будет находиться в пределах кода библиотеки B, а не для конечного пользователя.
Нижняя строка: boost::reference_wrapper
кажется более безопасной, объявив ее конструктор T&
как явный и предотвратит появление такой ошибки. Решение удалить явное ограничение конструктора в std::reference_wrapper
похоже на то, что оно скомпрометировало безопасность для удобства, что редко должно возникать в дизайне языка/библиотеки.