boost::reference_wrapper<T>有一个显式的 T&构造函数,而std::reference_wrapper<T>有一个隐式的构造函数.因此,在以下代码中:
foo = bar;
Run Code Online (Sandbox Code Playgroud)
如果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;
}
Run Code Online (Sandbox Code Playgroud)
在新版本(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;
}
Run Code Online (Sandbox Code Playgroud)
最后,在一些使用该库的应用程序中:
// i is a std::reference_wrapper<int> passed to this template function or class
set_max(i, 7, 11);
Run Code Online (Sandbox Code Playgroud)
在此示例中,库在set_max不更改调用接口的情况下更改其实现.这将默默地破坏传递它的任何代码,std::reference_wrapper因为参数将不再转换为int&并且将"重新绑定"到悬空引用(a或b).
我的问题:为什么标准委员会选择允许隐式转换(T&从而std::reference_wrapper<T>不是跟随boost并使T&构造函数显式化)?
编辑:( 回应Jonathan Wakely提供的答案)......
原始演示(在上面的部分中)有意简洁,以显示细微的库更改如何导致使用std::reference_wrapper向应用程序引入错误.
下一个演示用于显示reference_wrapper"通过接口传递引用" 的真实世界合法用法,以回应Jonathan Wakely的观点.
类似的东西,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;
};
Run Code Online (Sandbox Code Playgroud)
一个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; }
}
};
}
Run Code Online (Sandbox Code Playgroud)
一些开发人员使用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
}
Run Code Online (Sandbox Code Playgroud)
请注意以下事项:
最终用户使用std::reference_wrapper其预期的方式.
单独地,没有任何代码存在错误或逻辑缺陷,并且所有内容都与Vendor B库的原始版本完美配合.
升级库时,boost :: reference_wrapper将无法编译,而std :: reference_wrapper会默默地引入可能会或可能不会在回归测试中捕获的错误.
跟踪这样的错误将是困难的,因为"重新绑定"不是一个内存错误,工具valgrind可以捕获.此外,滥用的实际位置std::reference_wrapper将在供应商B的库代码中,而不是最终用户.
底线: boost::reference_wrapper通过将其T&构造函数声明为显式,似乎更安全,并且可以防止引入此类错误.删除显式构造函数限制的决定std::reference_wrapper似乎为了方便而损害了安全性,这在语言/库设计中应该很少发生.
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)。