Lambda只是重载了运算符()的类



我读到的关于Lambda的书越多,就越听到人们说它们只是伪装的函数对象/函数子(除非它们不捕获任何东西,在这种情况下,它们只是免费的静态函数。我想在本地范围内编写lambda,并将它们传递给通用事件处理程序,后者会根据需要调用它们,我开始注意到,我几乎无法做传统函数对象允许我做的任何事情。)。如果我对这一点的理解是错误的,请告诉我,据我所知,我已经评论了一大堆你可以用函子做的事情,而不能用lambdas做的事情:

#include <iostream>
#include <vector>
struct MyFunctorClass
{
// Custom constructor, can't do with lambda
MyFunctorClass(int& capturedVariable) : capturedVariable(capturedVariable) 
{ std::cout << "I can do anything on construction.n"; }
// Overloading constructors, different ways to initialise function object, can't do with lambda
MyFunctorClass(int& capturedVariable, int sizeOfBuffer) : capturedVariable(capturedVariable) 
{ heapAllocation = new int[sizeOfBuffer]; }
// Custom destructor, can't do with lambda
~MyFunctorClass() { delete[] heapAllocation; }  
void operator()() { std::cout << "Standard calln"; }
void operator()(int arg) { std::cout << "Argument passed: " << arg << 'n'; }   
// operator() overloading, different ways to call the function object, can't do with lambda
int* heapAllocation;                // Have heap allocated resources, can't do with lambda
bool internalStateVariable = true;  // Initialise a member variable on construction, can't do with lambda
int& capturedVariable;              // I can access this variable directly with MyFunctorClass::capturedVariable = 7, can't do with lambda
};
int main()
{
int localVar = 0;
bool trueOrFalse = false;
{
MyFunctorClass* myFunctionObj = new MyFunctorClass(localVar, 100);  
// Can dynamically allocate function object, can't with lambda
auto lambda = new[&]() { localVar = 1; };   // Can't do?
lambda.trueOrFalse = true;      // trueOrFalse isn't member of lambda, even though it captured it, doesn't make sense
}   // Lambda object is destroyed here. My function object lives until I delete it.
return 0;
}
void holdFunctionObject(MyFunctorClass* funcObj)
{
static std::vector<MyFunctorClass*> list;
list.push_back(funcObj);    
// I can hold all the function objects forever, they'll never go out of scope unless I delete them, can't do with lambda
}

我觉得自己受到了限制,lambdas似乎只是一种声明函数"就位"的方式。它们也保存状态,但只能保存已经在作用域中的对象的状态,而不能创建新的对象。也不能像函子那样以任何特定的方式初始化。我说对了吗?因为它们看起来与只包含重载运算符()的类非常不同;

我不明白你的意思;是的,lambdas只是具有operator()的类的实例的语法糖,这些类遵循特定的模板1;是的,你不能用完全自定义的类做大多数你能做的事情,这是有充分理由的:如果你想做这些事情,你已经可以写一个常规的class了。

Lambdas采用了一种特殊的、广泛使用的模式(可以捕获状态的一次性函子)2,并为它们提供了极其方便的语法3。如果您需要成熟的类,自70年代中期以来,该语言就已经使用class关键字进行了介绍。

此外,需要注意的是,尽管C++中的lambdas在类方面实现了,但这两个概念背后的"世界观"是不同的;闭包植根于函数式编程,而不是OOP。它们背后的想法是为一个单一的功能(=动作)建模,以及在创建现场捕获的工作所需的数据;相反,对象首先是一个可变实体,它提供了一个接口来更改或查询其状态。闭包可以用对象来实现,对象可以用闭包来实现,这一事实既有趣又有趣(最终源于两者都是状态和代码的结合),但它们的出发点截然不同。使用其中一个本质上是理解您想要的主要是捕获状态的代码("动词"),还是主要是带有变异接口的"打包状态"("名词")。


  1. 这不仅仅是理解其实现的快捷方式,它们实际上是在标准中的这些术语中指定的
  2. 来自函数和闭包从一开始就是一流对象的语言的模式,因此产生的语法糖有助于以这种语言变体的风格编写代码
  3. 让我告诉你,这是一件幸事;由于不得不将C++11代码移植到C++03,因此对代码进行分解是一项令人沮丧的工作——一切都变得如此冗长,读取时结果"流动"得更糟。捕获状态特别可怕,通常情况下,您必须至少重新声明四次相同的东西——一次在类级别的字段声明中,一次在构造函数原型中,一个在初始值设定项列表中,另一次在构造器调用中。与lambda捕获列表进行比较

除非它们不捕获任何东西,在这种情况下,它们只是免费的静态函数

这是事实上不正确的。无捕获lambdas可以转换为函数指针,但它们与"自由静态函数"不同。它们仍然是对象,而静态函数不是(尽管你可以有指向函数的指针,但这并不能使函数成为C++意义上的对象)。

至于其他的,你似乎没有意识到"所有的lambda都是函子"one_answers"所有的函子都是lambda"之间的区别。前者是真的,后者不是,后者也不打算是真的。

Lambdas私人持有他们的国家。手写函子可以随心所欲地保持其状态。大多数人会认为具有公共可访问状态的函子是不好的形式,但如果这是你想要的,那就由你自己决定了。

通过使用lambda,您同意接受它的限制。lambda的类型是在声明时合成的,并且没有命名,因此您不能创建接受特定lambda的API(好吧,也许可以,但不应该)。如果希望用户传递特定的函子类型,则不应使用lambda。

但实际上。。。您需要用户多久传递一次特定的函子类型?大多数时候,您需要的是用户传递一些可以用特定签名调用的东西。这就是std::function的作用。或者,如果它是一个模板函数,您可以使用一个可以用特定签名调用的任意对象。

如果你真的需要用户传递一个特定的函子类型。。。但由于大多数接口不会受到如此限制,lambda是一个有效的解决方案。

Lambdas的核心是语法糖,它使编写公共函子变得容易。在某些情况下,lambda肯定无法满足您的需要。但由于羊羔肉覆盖了这些东西90%的用途,所以它们已经足够好了。

lambdas的主要目的是将某个进程的代码逻辑地放在要调用该进程的位置附近。这提供了代码相对于进程的位置。这对于回调、延续等来说很常见。

与手工编写的函子相比,它还允许此类代码轻松访问回调的本地作用域。这使得这样的过程感觉像是代码的一部分,而不必追踪函子类型的定义并读取其operator()实现来了解发生了什么


还有:

auto lambda = new[&]() { localVar = 1; };   // Can't do?

您可以这样做:

auto lambda = new auto([&]() { localVar = 1; });   // Can't do?

您曾多次声明不能使用lambda,但实际上可以。

在构造时初始化成员变量,不能使用lambda

好吧,由于c++14,捕获init就是一个东西:

// internalState is a bool
auto l = [internalState = true] {};

有堆分配的资源,不能使用lambda

这两个例子怎么样?

// Raw owning pointers are bad, don't do this
auto l1 = [rintptr = new int] {
// rintptr is a captured pointer, with heap allocated data
delete rintptr;
};
// Unique pointer is better in every way.
auto l2 = [uptr = std::make_unique<int>()] {
// uptr is a captured heap allocated int too
};

对于自定义析构函数,没有任何析构函数也是可以的。每个捕捉到的物体都应该自动清理。如果他们没有,创建一个RAII包装。

对于自定义构造函数,如果不使lambda进一步复杂化,就无法想象一个好的用例。请记住,lambdas并不意味着完全替换类,而是提供一种简单的方法来声明经典函子。

如果在构造一个特定的lambda时确实需要执行一些东西,可以使用两个lambda,一个返回另一个:

auto l = []{
// Execute what you need before returning the lambda
// Setup objects to be captured here
return []{};
}();

可以动态分配函数对象,不能使用lambda

这里需要的是std::function。它将正确管理lambda的使用寿命。

std::function<void()> func;
{
// The lambda is moved to func
func = []{};
}
// func still holds it's instance of the lambda.

此外,std::function是智能的。它甚至可以使用SBO保持lambda,尽可能避免分配,从而提高速度。

trueOrFalse不是lambda的成员,即使它捕获了它,也没有意义

这是因为lambda的成员是私有的。没关系,lambdas只公开它的呼叫运算符。如果你想从外部变异一个成员,你可以使用指针或引用来模拟它。

此外,由于所有lambda都是不同类型的实例,并且捕获往往使用局部变量的名称,因此更改局部变量的名字实际上可能会破坏代码的其他位置,这不是很理想。捕获是私有的,还可以确保您对捕获的数据保持控制。

我建议lambda返回类似上面的lambda。它将允许您设置捕获数据的值,而不必稍后对其进行变异。

operator()重载

泛型lambda执行基本情况:

auto overloaded = [](const auto& val) {
std::cout << val << std::endl;
};
overloaded(4); // prints 4
overloaded(6.8); // prints 6.8
overloaded("test"); // prints test

如果你有更复杂的情况,你可以通过继承它们来组成Lambda:

// C++17 for simplicity
template<typename... Ls>
struct overload : Ls... {
explicit overload(Ls... ls) noexcept : Ls{std::move(ls)}... {}
using Ls::operator()...;
};

现在你可以超载lambdas:

auto o = overload(
[]{},
[i = 0](int) {},
[&](double) {}
);

在这里,您甚至可以为每个过载设置一组不同的捕获。

您可以在C++14中实现相同的语法,但overload的实现略有不同,而且有点复杂。


你不能用lambda做什么,但你可以用函子做什么,就是在constexpr上下文中使用它们:

constexpr void foo() {
[]{}(); // Ill formed
}
struct MyFunctor {
constexpr MyFunctor() = default;
constexpr void operator()(){}
};
constexpr void bar() {
MyFunctor{}(); // okay
}

请注意,此限制已从C++17中删除。Lambdas可以是constexpr。

现在您不能用lambda做的另一件事是像在template中那样进行类型匹配。

auto lambda = [](auto someVec) {
using T = typename decltype(someVec)::value_type; // ugh...
};

而在C++2a中,你可以这样做:

auto lambda = []<typename T>(std::vector<T> someVec) {
// Yay! Only vectors and got T!
};

最新更新