C++ 中的 Lambda <-> 闭包等价
Lambda <-> closure equivalence in C++
我正在观看一个名为 Lambda? You Keep Using that Letter 的视频,作者是 Kevlin Henney,他在视频中指出闭包和对象在根本上是等价的:
然后他通过 this javascript code 证明了他的观点,将堆栈实现为闭包:
const newStack = () => {
const items = []
return {
depth: () => items.lengh,
top: () => items[0],
push: newTop => { items.unshift(newTop) },
pop: () => { items.shift() },
}
}
闭包与 class 相比的优势在于它的状态确实是隐藏的,而私有成员 "inaccessible" 多于 "hidden"。
我试图在 C++ 中做一些等效的事情。然而,似乎很难用C++表达这一点。
我现在的版本在那里,它有两个主要缺点:
确实可以编译,但不会起作用(内部shared_ptr
在闭包创建后立即释放)
有点冗长:depth、top、push 和 pop 重复了 3 次。
auto newStack = []() {
auto items = std::make_shared<std::stack<int>>();
auto depth = [&items]() { return items->size();};
auto top = [&items]() { return items->top(); };
auto push = [&items](int newTop) { items->push(newTop); };
auto pop = [&items]() { items->pop(); };
struct R {
decltype(depth) depth;
decltype(top) top;
decltype(push) push;
decltype(pop) pop;
};
return R{ depth, top, push, pop};
};
在 C++ 中有可行的方法吗?
是的,在 C++ 中当然有更好的方法:不要使用 lambda。
一个 lambda 表达式定义了一个 class。闭包是 class——一个对象的一个实例。我们不需要与其他语言进行比较就可以告诉我们这一点——这正是 C++ 中定义 lambda 和闭包的方式。 §[expr.prim.lambda.closure]:
The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.
但是(这是重要的一点)至少在 C++ 中,lambda 表达式定义了一个 class 和一个 非常 有限的 public 接口。具体来说,它提供 operator()
的重载,如果它没有捕获任何内容,则转换为指向函数的指针。如果它确实捕获了某些东西,它还会定义一个构造函数来进行捕获。当然,如果它捕获了东西,它会定义成员变量来保存它捕获的任何东西。
但这就是它真正定义的全部。这并不是说它在隐藏它可能包含的任何其他内容方面做得更好。就是它真的没有包含任何其他内容。
在你的例子中,你试图定义一个类型,它有四个独立的成员函数,它们都在它们共享的某个状态下运行。正如您所展示的那样,将状态外部化是有可能的,因此您拥有的东西几乎等同于一些 C 代码(或按该顺序的东西),它只包含数据和一些对该数据进行操作的函数。是的,您可以将它们组合到一个结构中,至少可以对具有成员函数的 class 进行一些模仿。
但是您几乎是在与系统(可以这么说)作斗争以在 C++ 中执行此操作。 Lambdas/closures(因为它们是在 C++ 中定义的)并不是为了让您定义具有多个独立入口点的事物,每个入口点对共享数据执行单独的操作。正如塞缪尔约翰逊的老话所说,“[它]就像一只用后腿走路的狗。它做得不好;但你会惊讶地发现它完全完成了。”
我正在观看一个名为 Lambda? You Keep Using that Letter 的视频,作者是 Kevlin Henney,他在视频中指出闭包和对象在根本上是等价的:
然后他通过 this javascript code 证明了他的观点,将堆栈实现为闭包:
const newStack = () => {
const items = []
return {
depth: () => items.lengh,
top: () => items[0],
push: newTop => { items.unshift(newTop) },
pop: () => { items.shift() },
}
}
闭包与 class 相比的优势在于它的状态确实是隐藏的,而私有成员 "inaccessible" 多于 "hidden"。
我试图在 C++ 中做一些等效的事情。然而,似乎很难用C++表达这一点。
我现在的版本在那里,它有两个主要缺点:
确实可以编译,但不会起作用(内部
shared_ptr
在闭包创建后立即释放)有点冗长:depth、top、push 和 pop 重复了 3 次。
auto newStack = []() {
auto items = std::make_shared<std::stack<int>>();
auto depth = [&items]() { return items->size();};
auto top = [&items]() { return items->top(); };
auto push = [&items](int newTop) { items->push(newTop); };
auto pop = [&items]() { items->pop(); };
struct R {
decltype(depth) depth;
decltype(top) top;
decltype(push) push;
decltype(pop) pop;
};
return R{ depth, top, push, pop};
};
在 C++ 中有可行的方法吗?
是的,在 C++ 中当然有更好的方法:不要使用 lambda。
一个 lambda 表达式定义了一个 class。闭包是 class——一个对象的一个实例。我们不需要与其他语言进行比较就可以告诉我们这一点——这正是 C++ 中定义 lambda 和闭包的方式。 §[expr.prim.lambda.closure]:
The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type, called the closure type, whose properties are described below.
但是(这是重要的一点)至少在 C++ 中,lambda 表达式定义了一个 class 和一个 非常 有限的 public 接口。具体来说,它提供 operator()
的重载,如果它没有捕获任何内容,则转换为指向函数的指针。如果它确实捕获了某些东西,它还会定义一个构造函数来进行捕获。当然,如果它捕获了东西,它会定义成员变量来保存它捕获的任何东西。
但这就是它真正定义的全部。这并不是说它在隐藏它可能包含的任何其他内容方面做得更好。就是它真的没有包含任何其他内容。
在你的例子中,你试图定义一个类型,它有四个独立的成员函数,它们都在它们共享的某个状态下运行。正如您所展示的那样,将状态外部化是有可能的,因此您拥有的东西几乎等同于一些 C 代码(或按该顺序的东西),它只包含数据和一些对该数据进行操作的函数。是的,您可以将它们组合到一个结构中,至少可以对具有成员函数的 class 进行一些模仿。
但是您几乎是在与系统(可以这么说)作斗争以在 C++ 中执行此操作。 Lambdas/closures(因为它们是在 C++ 中定义的)并不是为了让您定义具有多个独立入口点的事物,每个入口点对共享数据执行单独的操作。正如塞缪尔约翰逊的老话所说,“[它]就像一只用后腿走路的狗。它做得不好;但你会惊讶地发现它完全完成了。”