IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> Item 34: Prefer lambdas to std::bind. -> 正文阅读

[开发工具]Item 34: Prefer lambdas to std::bind.

Item 34: Prefer lambdas to std::bind.

Effective Modern C++ Item 34 的学习和解读。

C++11 的 std::bind 是对 C++98 std:bind1st 和 std::bind2nd 的继承,它在 2005 年以 TR1 文档形式非正式地成为标准库的一部分。因为,许多 C++ 程序员可能有十几年的 std::bind 使用经验,现在告诉他放弃使用 std::bind,多少可能有些不情愿。但是,本 Item 会告诉你使用 lambda 替代 std::bind 将是个更好的选择。

对于 C++11,除了个别边缘 case,lambda 表达式要比 std::bind 更有优势。而对于 C++14,lambda 则可以完全替代 std::bind。

lambda 第一个优势是代码的可读性更强。例如,我们有一个设置声音报警的函数:

// typedef for a point in time (see Item 9 for syntax)
using Time = std::chrono::steady_clock::time_point;
// see Item 10 for "enum class"
enum class Sound { Beep, Siren, Whistle };
// typedef for a length of time
using Duration = std::chrono::steady_clock::duration;
// at time t, make sound s for duration d
void setAlarm(Time t, Sound s, Duration d);

如果我们想在设置声音报警后 1h,关闭报警,并持续 30s。使用 lambda 表达式修正 setAlarm,可以实现如下:

// setSoundL ("L" for "lambda") is a function object allowing a
// sound to be specified for a 30-sec alarm to go off an hour
// after it's set
auto setSoundL =
  [](Sound s)
  {
    // make std::chrono components available w/o qualification
    using namespace std::chrono;
    setAlarm(steady_clock::now() + hours(1), // alarm to go off
             s, // in an hour for
             seconds(30)); // 30 seconds
  };

上述代码逻辑非常清楚。如果使用 C++14 字面值 std::literals 改写上面代码,可以更加简洁:

auto setSoundL =
  [](Sound s)
  {
    using namespace std::chrono;
    using namespace std::literals; // for C++14 suffixes
    setAlarm(steady_clock::now() + 1h, // C++14, but
             s, // same meaning
             30s); // as above
  };

如果使用 std::bind 直接替换 lambda 表达式,可以改写成如下:

using namespace std::chrono; // as above
using namespace std::literals;
using namespace std::placeholders; // needed for use of "_1"
auto setSoundB = // "B" for "bind"
  std::bind(setAlarm,
            steady_clock::now() + 1h, // incorrect! see below
            _1,
            30s);

首先,相较于 lambda 版本,使用 std::bind,函数调用和传参不那么明显。并且这里还有一个占位符 “_1”,使用 setSoundB 时候,你需要查阅 setAlarm 的函数申明,才知道这里的占位符的传参类型。

最重要的是这里的代码逻辑有问题。显然,我们期望的是在调用 setAlarm 时候计算表达式 steady_clock::now() + 1h 的值。但是,使用 std::bind 的时候,表达式 steady_clock::now() + 1h 是传递给 std::bind 而不是 setAlarm,这意味着,在调用 std::bind 的时候,表达式的值就被计算出来,然后保存在绑定对象内部。这就导致和在调用 setAlarm 时候计算表达式的期望不一致。可以再使用一个 std::bind 封装该表达式以延迟到 setAlarm 调用的时候才计算:

auto setSoundB =
  std::bind(setAlarm,
            std::bind(std::plus<>(), steady_clock::now(), 1h),  // C++14
            _1,
            30s);

注意到 std::plus<> 缺省了类型参数,这是 C++14 的新特性,如果是 C++11,则需要指定类型:

auto setSoundB =
  std::bind(setAlarm,
            std::bind(std::plus<steady_clock::time_point>(), // C++11
                      steady_clock::now(),
                      hours(1)),
            _1,
            seconds(30));

如果 setAlarm 增加一个重载版本:

enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);

先前 lambda 版本代码依然可以正常工作。但是,std::bind 将会产生编译报错。因为编译器无法确认传递哪个版本的 setAlarm。需要将 setAlarm 转换为合适的函数指针:

using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB =                                        // now
  std::bind(static_cast<SetAlarm3ParamType>(setAlarm),  // okay
            std::bind(std::plus<>(),
                      steady_clock::now(),
                      1h),
            _1,
            30s);

但是,这又引入了 std::bind 和 lambda 二者的不同。setSoundL 使用正常的函数调用来调用 setAlarm,编译器可以选择使用内联。

setSoundL(Sound::Siren); // body of setAlarm may
                         // well be inlined here

但是 std::bind 不可以,setSoundB 使用函数指针调用调用 setAlarm,这是运行期的行为,无法被内联。

setSoundB(Sound::Siren); // body of setAlarm is less
                         // likely to be inlined here

这就是使用 lambda 的第二个优势:代码的性能可能会更好。

使用 lambda 的第三个优势是代码更容易理解。看下面的例子:

enum class CompLevel { Low, Normal, High }; // compression level
Widget compress(const Widget& w, // make compressed
                CompLevel lev);  // copy of w

假设我们想创建一个函数对象,用来指定特定 Widget 的压缩等级。使用 std::bind 创建函数对象:

Widget w;
using namespace std::placeholders;
auto compressRateB = std::bind(compress, w, _1);

传递 w 给 std::bind,那么 w 如何存放在 compressRateB 内部的呢?是传值还是传引用?如果 w 在调用 std::bindcompressRateB 之间发生改变,传引用的方式将导致结果的不同。

std::bind 默认是拷贝它的参数到绑定对象内,用户可以使用 std::ref 指定传引用:

auto compressRateB = std::bind(compress, std::ref(w), _1);

这就需要你了解 std::bind 实现机制。但对于 lambda 的实现版本,w 是值捕获还是引用捕获非常明显:

auto compressRateL =            // w is captured by
  [w](CompLevel lev)            // value; lev is
  { return compress(w, lev); }; // passed by value

同样明显的是参数如何传递给 lambda 的。这里,很明显 lev 是值传递:

compressRateL(CompLevel::High); // arg is passed
                                // by value

但是,std::bind 的绑定对象的调用,参数是如何传递的?

compressRateB(CompLevel::High); // how is arg
                                // passed?

答案是引用传递,这就需要你了解 std::bind 的工作机制:std::bind 绑定对象的函数调用使用了完美转发机制。

通过上述比较我们可以看到,相较于使用 std::bind,使用 lambda 表达式的代码可读性更强、更容易理解、性能可能更好。对于 C++14,你没有理由不选择使用 lambda。对于 C++11,只有两种场景,std::bind 可以弥补 lambda 的不足:

第一:移动捕获。C++14 的初始化捕获模式支持移动捕获。C++11 的 lambda 不支持移动捕获,可以使用 std::bind 模拟来间接实现,参见 Item32

第二:多态函数对象。C++14 支持 auto 参数类型,也即通用 lambda,参见 Item33 。但是 C++11 不支持通用 lambda。而 std::bind 绑定对象的函数调用使用完美转发实现,可以接收任何类型的参数。如下例子:

class PolyWidget {
public:
  template<typename T>
  void operator()(const T& param);};

PolyWidget pw;
auto boundPW = std::bind(pw, _1);

boundPW(1930);       // pass int to
                     // PolyWidget::operator()
boundPW(nullptr);    // pass nullptr to
                     // PolyWidget::operator()
boundPW("Rosebud");  // pass string literal to
                     // PolyWidget::operator()

C++11 做不到,C++14 则很容易:

auto boundPW = [pw](const auto& param) // C++14
               { pw(param); };

最后总结下:

  • 相较于 std::bind,lambda 代码可读性更强、更容易理解、性能可能更好。
  • C++11 的 std::bind 在实现移动捕获、模板函数对象方面可以弥补 lambda 的不足。
  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2022-06-06 17:30:21  更:2022-06-06 17:30:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 15:56:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码