24

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

foo = bar;

如果foo是 a boost::reference_wrapper,则代码将无法编译(这很好,因为reference_wrapper实际引用的语义不同。

如果foo是 a 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&,而是“重新绑定”到一个悬空引用(ab)。

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


编辑:( 回应乔纳森维克利提供的答案)......

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

reference_wrapper为了响应 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被更改为更通用,而没有更改其调用接口。出于本演示的目的,旧实现定义在命名空间中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预期的方式。

  • 单独来看,没有任何代码有错误或逻辑缺陷,并且一切都与 Vendor B的库的原始版本完美配合。

  • boost::reference_wrapper 在升级库时将无法编译,而 std::reference_wrapper 默默地引入了一个可能会或可能不会在回归测试中发现的错误。

  • 跟踪这样的错误将很困难,因为“重新绑定”不是诸如工具之类的工具会捕获的内存错误。valgrind此外,滥用的实际站点std::reference_wrapper将在供应商B的库代码中,而不是最终用户的。

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

4

2 回答 2

3

这将默默地破坏任何传递它的代码,std::reference_wrapper因为参数将不再转换为int&,而是“重新绑定”到一个悬空引用(a 或 b)。

所以不要那样做。

reference_wrapper用于通过接口传递引用,否则会产生按值复制,而不是用于传递任意代码。

还:

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

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

于 2013-03-27T09:48:08.977 回答
2

Nate Kohl 提供的DR-689链接充分解释了隐式转换( T&--> reference_wrapper<T>) 允许std::reference_wrapper<T>不允许的原因。总结一下: boost::reference_wrapper<T>

2007 年,C++0x/C++11库工作组(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 似乎将这种变化简单地视为安全性和便利性之间的可接受折衷(与其对应的 boost 相比)。

  • 目前尚不清楚 LWG 是否预见到其他潜在风险(例如本页提供的示例中所展示的风险),因为 DR-689 中提到的唯一风险是绑定到右值(如上一条条目中所述和解决的,DR -688)。

于 2013-04-01T22:24:52.003 回答