隐含的T&amp; std :: reference_wrapper <t>的构造函数使用它很危险吗?</t>

时间:2013-03-26 20:24:21

标签: c++ c++11

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

foo = bar;

如果fooboost::reference_wrapper,代码将无法编译(这很好,因为reference_wrapper 具有与实际相同的语义参考

如果foostd::reference_wrapper,则代码将&#34;重新绑定&#34; foobar的引用(而不是像人们可能错误地指望的那样分配值)。

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

在某个假设库的 1.0 版本中:

void set_max(int& i, int a, int b) {
    i = (a > b) ? a : b;
}

在新的版本(的 1.1 ),set_max被转换为模板接受任何宽度的整数(或UDT&#39;多个),而不改变该接口:

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&而反而会#34;重新绑定&#34;悬挂参考(ab)。

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


修改: (响应Jonathan Wakely提供的答案)......

原始演示(在上面的部分中)有意简洁,以显示细微的库更改如何导致使用T&向应用程序引入错误。

下一个演示提供给显示真实世界,合法使用std::reference_wrapper对于&#34;传递引用通过接口&#34 ;,响应于乔纳森Wakely&#39; S点

  • 来自开发者/供应商A

reference_wrapper类似的东西,但假装它专门用于某项任务:

std::bind
  • 来自开发者/供应商B

一个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; }; 仿函数类。在这个虚构库的版本1.0和1.1之间,RunningMax的实现被更改为更通用,而不更改其调用接口。 出于本演示的目的,旧实现在命名空间RunningMax中定义,而新实现在lib_v1中定义:

lib_v2
  • 最后但并非最不重要的是,以上所有代码的最终用户:

一些开发人员使用Vendor / Developer A和B中的代码完成某项任务:

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; }
            }
    };
}


请注意以下事项:

  • 最终用户使用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 } 的方式。

  • 独立下,没有一个代码具有错误或逻辑上的缺陷,并且一切与卖主的 B的原始版本完美地工作&#39; S库。

  • 升压::的reference_wrapper会失败,在升级库来编译,而标准::的reference_wrapper默默地介绍可能或可能不会在回归测试捕获的错误。

  • 追踪这样的错误很困难,因为&#34;重新绑定&#34; std::reference_wrapper之类的工具不会捕获内存错误。此外,valgrind。将内供应商的&#39的误操作的实际现场; S库代码,而不是终端用户&#39; S

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

2 个答案:

答案 0 :(得分: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不会改变任何内容,它不会影响引用包装器。

答案 1 :(得分:2)

隐式转化T& - &gt; reference_wrapper<T>)的原因允许std::reference_wrapper<T>但不是 {{ 1}},在Nate Kohl提供的DR-689链接中得到了充分的解释。总结一下:

2007年,C ++ 0x / C ++ 11 图书馆工作组(LWG)建议将# DR-689 更改为boost::reference_wrapper<T>部分标准:

  

reference_wrapper的构造函数目前是显式的。   这背后的主要动机是尊重的安全问题   rvalues,由[DR-688]的拟议决议案解决。   因此,我们应该 考虑放宽 的要求   在构造函数上,因为隐式转换的请求保持不变   重修表面。

     

建议解决方案:从reference_wrapper的构造函数中删除显式。

值得指出:

  • 20.8.3.1 [refwrap.const]以这种方式拥有not been relaxed,似乎也没有针对它的提案,这会在boost::reference_wrapper和{{1}的语义之间产生不一致}}

  • 根据DR-689中的措辞(特别是“请求保持浮出水面”部分),似乎LWG可以简单地将此变化视为安全性和便利性之间可接受的权衡(与其提升相反)对应)。

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