boost::reference_wrapper<T>
有显式 T&
构造函数,而std::reference_wrapper<T>
有隐式构造函数。因此,在以下代码中:
foo = bar;
如果foo
是boost::reference_wrapper
,代码将无法编译(这很好,因为reference_wrapper
不具有与实际相同的语义参考
如果foo
是std::reference_wrapper
,则代码将&#34;重新绑定&#34; foo
对bar
的引用(而不是像人们可能错误地指望的那样分配值)。
这可能导致难以捉摸的错误......请考虑以下示例:
在某个假设库的 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;悬挂参考(a
或b
)。
我的问题:为什么标准委员会选择允许隐式转换(从T&
到std::reference_wrapper<T>
),而不是关注boost
并制作{{ 1}}构造函数显式?
修改: (响应Jonathan Wakely提供的答案)......
原始演示(在上面的部分中)有意简洁,以显示细微的库更改如何导致使用T&
向应用程序引入错误。
下一个演示提供给显示真实世界,合法使用std::reference_wrapper
对于&#34;传递引用通过接口&#34 ;,响应于乔纳森Wakely&#39; S点
与reference_wrapper
类似的东西,但假装它专门用于某项任务:
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;
};
仿函数类。在这个虚构库的版本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&
中删除显式构造函数限制的决定似乎为了方便而损害了安全性,这在语言/库设计中应该很少发生。
答案 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)。