什么样的网站空间做电影网站不卡,藁城网站建设,网站建设新手如何自己做网站,杭州建设工程交易平台使用 Lambda 使代码更具表现力 一、Lambda VS. 仿函数二、总结 一、Lambda VS. 仿函数
Lambda 是 C11 中最引人注目的语言特性之一。它是一个强大的工具#xff0c;但必须正确使用才能使代码更具表现力#xff0c;而不是更难理解。
首先#xff0c;要明确的是#xff0c;… 使用 Lambda 使代码更具表现力 一、Lambda VS. 仿函数二、总结 一、Lambda VS. 仿函数
Lambda 是 C11 中最引人注目的语言特性之一。它是一个强大的工具但必须正确使用才能使代码更具表现力而不是更难理解。
首先要明确的是Lambda 并没有为语言添加新的功能。任何可以用 Lambda 完成的事情都可以用仿函数Functor来完成虽然仿函数的语法更繁琐需要更多的类型声明。
例如比较检查一个整数集合中所有元素是否都在两个整数 a 和 b 之间的两种方式
仿函数。Lambda 表达式。
仿函数版本
class IsBetween
{
public:IsBetween(int a, int b) : a_(a), b_(b) {}bool operator()(int x) { return a_ x x b_; }
private:int a_;int b_;
};bool allBetweenAandB std::all_of(numbers.begin(), numbers.end(), IsBetween(a, b));Lambda 版本
bool allBetweenAandB std::all_of(numbers.begin(), numbers.end(),[a,b](int x) { return a x x b; });很明显Lambda 版本更简洁更易于编写这可能是 Lambda 在 C 中备受关注的原因。
对于像检查一个数字是否在两个边界之间这样简单的操作许多人可能会同意 Lambda 是更好的选择。但也并非所有情况下都是如此。
除了编写和简洁性之外在前面的例子中Lambda 和仿函数之间的两个主要区别是
Lambda 没有名字。Lambda 不隐藏其代码而是直接在调用点展示。
但是通过调用具有有意义名称的函数将代码从调用点移出是管理抽象级别的一种基本技巧。但是上面的例子是可以接受的因为这两个表达式
IsBetween(a, b)和
[a,b](int x) { return a x x b; }读起来很相似。它们的抽象级别是一致的。
但是当代码变得更加复杂时结果就会大不相同以下例子将说明这一点。
一个表示盒子的类的例子它可以根据尺寸和材质金属、塑料、木材等进行构建并提供对盒子特性的访问
class Box
{
public:Box(double length, double width, double height, Material material);double getVolume() const;double getSidesSurface() const;Material getMaterial() const;
private:double length_;double width_;double height_;Material material_;
};有一个这样的盒子集合
std::vectorBox boxes ....想要选择能够安全地容纳某种产品水、油、果汁等的盒子。
通过一些物理推理可以近似地将产品对盒子四个侧面的压力视为产品的重量它分布在这些侧面的表面上。如果材料能够承受施加的压力则盒子足够坚固。
假设材料可以承受的最大压力为
class Material
{
public:double getMaxPressure() const;....
};产品提供了它的密度以便计算它的重量
class Product
{
public:double getDensity() const;....
};现在要选择能够安全地容纳产品 product 的盒子可以使用 STL 和 Lambda 编写以下代码
std::vectorBox goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes),[product](const Box box){const double volume box.getVolume();const double weight volume * product.getDensity();const double sidesSurface box.getSidesSurface();const double pressure weight / sidesSurface;const double maxPressure box.getMaterial().getMaxPressure();return pressure maxPressure;});以下是等效的仿函数定义
class Resists
{
public:explicit Resists(const Product product) : product_(product) {}bool operator()(const Box box){const double volume box.getVolume();const double weight volume * product_.getDensity();const double sidesSurface box.getSidesSurface();const double pressure weight / sidesSurface;const double maxPressure box.getMaterial().getMaxPressure();return pressure maxPressure;}
private:Product product_;
};在主代码中
std::vectorBox goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), Resists(product));尽管仿函数仍然需要更多的类型声明但使用仿函数的算法代码行看起来比使用 Lambda 更清晰。不幸的是对于 Lambda 版本来说这一行代码更重要因为它是主要代码。
在这里Lambda 的问题在于它展示了如何进行盒子检查而不是简单地说检查已经完成因此它的抽象级别太低了。在该示例中它会影响代码的可读性因为它迫使读者深入 Lambda 的主体以弄清楚它做了什么而不是简单地说明它做了什么。
在这里有必要将代码从调用点隐藏并为它赋予一个有意义的名称。仿函数在这方面做得更好。
但这是否意味着不应该在任何非平凡的情况下使用 Lambda当然不是。
Lambda 被设计得比仿函数更轻便、更方便同时仍然保持抽象级别有序。这里的技巧是通过使用中间函数将 Lambda 的代码隐藏在一个有意义的名称后面。以下是 C14 中实现此目的的方法
auto resists(const Product product)
{return [product](const Box box){const double volume box.getVolume();const double weight volume * product.getDensity();const double sidesSurface box.getSidesSurface();const double pressure weight / sidesSurface;const double maxPressure box.getMaterial().getMaxPressure();return pressure maxPressure;};
}在这里Lambda 被封装在一个函数中该函数只是创建它并返回它。这个函数的作用是将 Lambda 隐藏在一个有意义的名称后面。
以下是主代码它从实现负担中解脱出来
std::vectorBox goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));现在为了使代码更具表现力在本文的其余部分使用范围Range而不是 STL 迭代器
auto goodBoxes boxes | ranges::view::filter(resists(product));当调用算法周围有其他代码时隐藏实现的必要性变得更加重要。为了说明这一点添加一个要求即盒子必须从用逗号分隔的文本测量描述例如“16,12.2,5”和所有盒子的唯一材料进行初始化。
如果直接调用即时 Lambda结果将如下所示
auto goodBoxes boxesDescriptions| ranges::view::transform([material](std::string const textualDescription){std::vectorstd::string strSizes;boost::split(strSizes, textualDescription, [](char c){ return c ,; });const auto sizes strSizes | ranges::view::transform([](const std::string s) {return std::stod(s); });if (sizes.size() ! 3) throw InvalidBoxDescription(textualDescription);return Box(sizes[0], sizes[1], sizes[2], material);})| ranges::view::filter([product](Box const box){const double volume box.getVolume();const double weight volume * product.getDensity();const double sidesSurface box.getSidesSurface();const double pressure weight / sidesSurface;const double maxPressure box.getMaterial().getMaxPressure();return pressure maxPressure;});这变得非常难以阅读。但是通过使用中间函数来封装 Lambda代码将变成
auto goodBoxes textualDescriptions | ranges::view::transform(createBox(material))| ranges::view::filter(resists(product));这才是希望代码呈现的样子。
请注意这种技术在 C14 中有效但在 C11 中略有不同。
Lambda 的类型没有在标准中指定而是由编译器的实现决定。这里auto 作为返回值类型允许编译器将函数的返回值类型写为 Lambda 的类型。但在 C11 中不能这样做因此需要指定一些返回值类型。Lambda 可以隐式转换为具有正确类型参数的 std::function并且可以在 STL 和范围算法中使用。请注意std::function 会带来与堆分配和虚拟调用间接相关的额外成本。
在 C11 中resists 函数的建议代码将是
std::functionbool(const Box) resists(const Product product)
{return [product](const Box box){const double volume box.getVolume();const double weight volume * product.getDensity();const double sidesSurface box.getSidesSurface();const double pressure weight / sidesSurface;const double maxPressure box.getMaterial().getMaxPressure();return pressure maxPressure;};
}请注意在 C11 和 C14 的实现中resists 函数返回的 Lambda 可能不会被复制因为返回值优化可能会优化掉它。还要注意返回 auto 的函数必须在其调用点可见。因此这种技术最适合在与调用代码相同的文件中定义的 Lambda。
二、总结
对于对抽象级别透明的函数请使用在调用点定义的匿名 Lambda。否则将 Lambda 封装在一个中间函数中。