c++ - std::reference_wrapper<T> 的隐式 T& 构造函数是否会使使用变得危险?

标签 c++ c++11

boost::reference_wrapper<T>有一个 显式 T&构造函数,而 std::reference_wrapper<T>有一个 隐式 一。因此,在以下代码中:

foo = bar;

fooboost::reference_wrapper ,代码将无法编译(这很好,因为 reference_wrapper 确实 而不是 具有与实际引用相同的语义。

foostd::reference_wrapper ,代码将“重新绑定(bind)”foo引用 bar (而不是像人们可能错误地期望的那样分配值)。

这可能会导致难以捉摸的错误......考虑以下示例:

在版本 1.0 一些假设的图书馆:
void set_max(int& i, int a, int b) {
    i = (a > b) ? a : b;
}

在新版本 ( 1.1 ) 中, set_max转换为模板以接受任何宽度(或 UDT)的整数,而无需更改接口(interface):
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&而是“重新绑定(bind)”到悬空引用( ab )。

我的问题:为什么标准委员会选择允许隐式转换(从 T&std::reference_wrapper<T>)而不是遵循 boost并制作 T&构造函数显式?

编辑: (回应乔纳森·韦克利(Jonathan Wakely)提供的答案)...

原始演示(在上一节中)有意简洁地展示了微妙的库更改如何导致使用 std::reference_wrapper将错误引入应用程序。

提供下一个演示以展示 reference_wrapper 的真实合法使用。用于“通过接口(interface)传递引用”,以回应 Jonathan Wakely 的观点。
  • 来自开发商/供应商 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

  • 一个 RunningMax仿函数类。在这个虚构库的 1.0 和 1.1 版本之间,RunningMax 的实现更改为更通用,而不更改其调用接口(interface)。 出于本演示的目的 ,旧的实现定义在命名空间 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; }
                }
        };
    }
    
  • 最后但并非最不重要的是,上述所有代码的最终用户:

  • 一些开发人员使用供应商/开发人员 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它的预期方式。
  • 个人 ,所有代码都没有错误或逻辑缺陷,一切都与供应商的原始版本完美配合的图书馆。
  • boost::reference_wrapper 在升级库时将无法编译,而 std::reference_wrapper 会默默地引入一个错误,该错误可能会或可能不会在回归测试中被捕获。
  • 跟踪这样的错误会很困难,因为“重新绑定(bind)”不是诸如 valgrind 之类的工具的内存错误。会捕获。此外,实际网站的滥用std::reference_wrapper将在供应商内 的库代码,而不是最终用户的。

  • 底线: boost::reference_wrapper通过声明它的 T& 似乎更安全构造函数作为显式,并会防止引入这样的错误。决定在 std::reference_wrapper 中删除显式构造函数限制似乎为了方便而损害了安全性,这在语言/库设计中应该很少发生。

    最佳答案

    This would silently break any code that passes it a std::reference_wrapper as the argument would no longer convert to int& and would instead "rebind" to a dangling reference (a or b).



    所以不要那样做。
    reference_wrapper用于通过接口(interface)传递引用,否则会进行按值复制,而不是传递给任意代码。

    还:

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


    decay不会改变任何东西,它不会影响引用包装器。

    关于c++ - std::reference_wrapper<T> 的隐式 T& 构造函数是否会使使用变得危险?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15646681/

    相关文章:

    c++ - 使用 Microsoft Visual C++ 构建工具的 CMake

    c++ - 用于访问 BeagleBone Black 的库(3.8 Kerne - Angstrom)

    c++ - 描述 `consteval` 函数参数的名称在编译时已知,但 constexpr 未知

    c++ - 一个比较奇怪的现象Excel MFC ADO数据库编程

    C++类成员初始化&构造函数定义

    c++ - 将 long int* 转换为 long long int*

    c# - 从 C# 访问 C++ 类枚举值

    C++11 在类中放置枚举类的重载运算符

    c++ - 我可以检查初始化列表中设置的构造函数主体中的变量吗?

    c++ - 如何将左值转换为右值? `new` 左值会发生什么?