假设我们有个函数来设置声音警报:
using Time = std;:chrono::steady_clock::time_point;
enum class Sound { Beep, Siren, Whistle };
using Duration = std::chrono::steady_clock::duration;
void setAlarm(Time t, Sound s, Duration d);
进一步假设,在程序的某处我们想设置在一小时后发出警报并持续30秒,警报的具体声音尚未决定。这么依赖,我们可以撰写一个lambda表达式,修改 setAlarm
的接口,这个新的接口只需指定声音即可:
auto setSoundL =
[](Sound s)
{
using namespace std::chrono;
using namespace std::literals;
setAlarm(steady_clock::now() + 1h,
s,
30s);
};
下面是尝试使用 std::bind
实现的代码:
using namespace std::chrono;
using namespace std::literals;
auto setSoundB =
std::bind(setAlarm,
steady_clock::now() + 1h, // error!
std::placeholders::_1,
30s);
相比于lambda表达式,std::bind
的实现方式降低了代码的可读性。还有一项错误在于,在lambda表达式中, steady_clock::now() + 1h
在 setAlarm
被调用的时刻评估求值。但在 std::bind
中, steady_clock::now + 1h
作为 std::bind
的参数,在绑定时被求值,最终导致的结果是,警报被设定的启动时刻是在调用 std::bind
的时刻后的一个小时,而非 setAlarm
被调用的时刻后的一小时。
为了解决这个问题,就需要使用 std::bind
的延迟求值:
auto setSoundB =
std::bind(setAlarm,
std::bind(std::plus<>(), steady_clock::now(), 1h),
std::placeholders::_1,
30s);
一旦对 setAlarm
实施重载,新的问题马上出现。假设有个重载版本接收第四个形参,指定警报的音量:
enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);
lambda表达式的版本会正常运作,重载决议会选择三参版本的 setAlarm
。而 std::bind
的调用就无法通过编译了,问题在于编译器不知道该选择哪个版本。
为了使得 std::bind
能够通过编译, setAlarm
必须强制转换到适当的函数指针类型:
using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB =
std::bind(static_cast<SetAlarm3ParamType>(setAlarm),
std::bind(std::plus<>(), steady_clock::now(), 1h),
std::placeholders::_1,
30s);
在编译器优化层面,lambda表达式版本的代码量更小,结构更简洁,编译器可能会将其内联,而 std::bind
的内联优化可能较小。
std::bind
的灵活性也较低,返回的结果对象里,绑定的变量是按值存储的;调用绑定对象时,形参是按引用传递的,而绑定对象的函数调用运算符运用了完美转发。当然,也可以采用 std::ref
的手法按引用传递绑定的变量:
auto compressRateB = std::bind(compress, std::red(w), _1);
在C++14中,根本没有 std::bind
的适当用力。而在C++11中,std::bind
仅有两个受限的场合还有使用的理由:
- 移动捕获。 参考条款32。
- 多态函数对象。 因为绑定对象的函数调用运算符利用了完美转发,它就可以接收任何类型的实参。在你想要绑定的对象具有一个函数调用运算符模板时,是有利用价值的。例如,给定一个类:
class PolyWidget {
public:
template<typename T>
void operator()(const T& param);
...
};
std::bind
可以采用如下方式绑定 PolyWidget
类型对象:
PolyWidget pw;
auto boundPW = std::bind(pw, _1);
这么一来, boundPW
就可以通过任意类型的实参加以调用:
boundPW(1930);
boundPW(nullptr);
boundPW("Rose")
在C++14中,使用带有 auto
形参的lambda表达式就可以达到相同的效果:
auto boundPW = [pw](const auto&& param)
{ pw(param); };