关于std::shuffle标准实现中转发引用与using声明的技术问题
std::shuffle 实现相关疑问解答
以下是公开的
std::shuffle标准参考实现:
template<class RandomIt, class URBG> void shuffle(RandomIt first, RandomIt last, URBG&& g) { typedef typename std::iterator_traits<RandomIt>::difference_type diff_t; typedef std::uniform_int_distribution<diff_t> distr_t; typedef typename distr_t::param_type param_t; distr_t D; diff_t n = last - first; for (diff_t i = n-1; i > 0; --i) { using std::swap; swap(first[i], first[D(g, param_t(0, i))]); } }
本次解答针对以下两个问题展开:
- 该实现中随机数生成器参数使用了转发引用
URBG&& g,但未使用std::forward<URBG>(g)对生成器做左值/右值转发,请问这样设计的原因是什么? - 代码中
using std::swap声明放在循环体内部而非外部,请问这样设计的考量是什么?放在循环内是否会影响性能?
问题1解答
- 核心原因是C++标准对随机数分布的
operator()有明确要求:必须接收左值类型的随机数生成器(URBG)作为入参,即使传入右值生成器也无法完成调用。 - 这里的转发引用
URBG&&作用只是为了同时兼容左值和右值的实参传入,避免模板参数推导限制过严,并不是为了做完美转发。所有实参绑定到变量g之后都是左值,刚好符合分布的operator()的入参要求,不需要再做转发。 - 额外补充:随机数生成器是有内部状态的,每次调用都会修改其状态,即使传入的是右值生成器,我们也需要保留修改后的状态,用左值调用完全符合逻辑,完美转发在这里没有使用价值,反而可能导致调用失败。
问题2解答
- 首先说放置考量:这是C++调用
swap的标准惯用法,将std::swap引入当前作用域后,编译器会优先根据swap入参的类型触发参数依赖查找(ADL),匹配到类型自定义的swap实现,如果找不到才会 fallback 到标准std::swap,保证了最佳适配性。using std::swap放在循环内部和函数开头的作用完全一致,放在调用位置附近只是为了可读性更好,能直观看到这个声明的作用是服务于下方的swap调用。 - 关于性能影响:完全没有任何运行时开销。
using是编译期语法,只会影响编译阶段的名字查找规则,不会生成任何运行时代码,不管放在循环内外,最终生成的二进制指令没有任何区别。
内容的提问来源于stack exchange,提问作者Itachi Uchiwa
相关产品推荐
相关产品推荐

