Подтвердить что ты не робот

Неявный T & конструктор std:: reference_wrapper <T> делает его опасным для использования?

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 похоже на то, что оно скомпрометировало безопасность для удобства, что редко должно возникать в дизайне языка/библиотеки.

4b9b3361

Ответ 1

Причина неявного преобразования (T&reference_wrapper<T>) разрешена для std::reference_wrapper<T>, , но не boost::reference_wrapper<T>, достаточно объяснена в DR-689, предоставленная Нейтом Колем. Подводя итог:

В 2007 году рабочая группа С++ 0x/С++ 11 Library (LWG) предложила изменить # DR-689 на раздел 20.8.3.1 [refwrap.const] стандарта:

Конструктор reference_wrapper в настоящее время явно. Главной мотивацией для этого является проблема безопасности с уважением к значениям, которые рассматриваются в предлагаемой резолюции [DR-688]. Поэтому мы должны рассмотреть возможность расслабления требований на конструкторе, так как запросы на неявное преобразование сохраняются шлифовка.

Предлагаемое разрешение: Удалить явное из конструктора reference_wrapper.

Стоит отметить:

  • boost::reference_wrapper таким образом не был расслаблен и не существует предложение для него, которое создает несогласованность между семантикой boost::reference_wrapper и std::reference_wrapper.

  • Основываясь на формулировке в DR-689 (в частности, часть "запросы продолжают работать" ), кажется вероятным, что это изменение просто рассматривалось LWG как приемлемый компромисс между безопасностью и удобством (в отличие от его повышения аналог).

  • Неясно, ожидали ли LWG другие потенциальные риски (например, продемонстрированные в примерах, приведенных на этой странице), поскольку единственным риском, упомянутым в DR-689, является риск привязки к rvalue (как описано и разрешено в предыдущей записи, DR-688).

Ответ 2

Это беззвучно нарушит любой код, который передает ему std::reference_wrapper, поскольку аргумент больше не будет преобразовываться в int& и вместо этого "переподдерживается" на оборванную ссылку (a или b).

Так что не делайте этого.

reference_wrapper предназначен для передачи ссылок через интерфейсы, которые в противном случае могли бы сделать постовую копию, а не для передачи произвольного кода.

также:

// i is a std::reference_wrapper<int> (perhaps b/c std::decay wasn't used)

decay ничего не изменит, это не повлияет на ссылочные оболочки.