使用 lambda 函数来定义非常小的辅助函数是好的风格吗?

Is it good style to use lambda functions to define very small helper functions?

提问人:user904963 提问时间:11/12/2013 最后编辑:dypuser904963 更新时间:1/10/2023 访问量:2613

问:

举个愚蠢的例子,假设我有一个函数,出于某种原因,我需要对 中的几个操作进行几次操作。与其将辅助函数放在其他地方(这可能会增加混乱并损害可读性),不如这样做有什么优点和缺点(效率、可读性、可维护性等):int f(vector<int> v)vf

int f(vector<int> v)
{
    auto make_unique  = [](vector<int> &v)
    {
        sort(begin(v), end(v));
        auto unique_end = unique(begin(v), end(v));
        v.erase(unique_end, end(v));
    };
    auto print_vector = [](vector<int> const &v)
    {
        copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
        cout << endl;
    };

   make_unique (v);
   print_vector(v);
   // And then the function uses these helpers a few more times to justify making
   // functions...
}

还是有一些首选的替代方案?

C++ C++11 编码风格的 Lambda

评论

0赞 aaronman 11/12/2013
这几乎是基于意见的,尝试重新措辞,以便客观地回答
0赞 user904963 11/12/2013
我想在风格上考虑一下,这些情况并不像把你的{放在哪里那么简单,可读性就不那么主观了。然后它变成了多数规则类型的东西,因为你正在编写代码供其他编码人员阅读。
0赞 aaronman 11/12/2013
问题是我可以用一种方式回答这个问题,而有人可以给出一个完全相反的解释。至少试着缩小一点范围
0赞 user904963 11/12/2013
我明白你的意思,但我想不出如何以这样一种方式重新表述一个不荒谬的风格问题,以至于它有一个“正确答案”。你有什么建议吗?
1赞 aaronman 11/12/2013
只是一个提示,不要命名函数make_unique

答:

2赞 aaronman 11/12/2013 #1

效率:
这基本上是函数与函子,这就是 lambda 在引擎盖下的内容。函子实际上可以更快,这是因为它们更容易内联,并非所有编译器都不会内联函数指针(尽管这是可能的)。这样做的原因是,当您传递函数指针时,编译器只知道函数的类型,而函子具有整个函数,因为类型是唯一的。

可读性:
这部分或多或少是基于意见的。函数指针 IMO 非常烦人,它们有丑陋的类型,并且不像函子那样通用。例如,函子可以很容易地在运行时传递。另一方面,写出一个完整的函数比一个大的 lambda 更具可读性。

我想说的最后一点是,像函子一样的 lambda 可以有一个状态(不像函数,除非你计算静态变量),使用 lambda 你可以捕获一个变量。由于编译器内联的限制,最好不要传递函数指针。

所以我想说,对于传递给 stdlib 的小函数,最好使用 lambdas,而对于实现函子的大函数,函数指针在 c++11 中真的不再是惯用的。

不幸的是,在我看来,您使用它们的方式并不好。编译器将它们作为普通函数内联是没有问题的。一个名为 or 的函数更适合它自己的实际功能,因为它可以在许多其他地方使用。print_vectormake_unique

可维护性:
在这种情况下,我会说 lambda 的可维护性很低,因为它们不能在函数之外重用,而且它们看起来很混乱,因为它们挤满了它们所在的函数。因此,最好在外面定义它们。

评论

1赞 Yakk - Adam Nevraumont 11/12/2013
OP 不会在任何地方传递这些函子。
0赞 aaronman 11/12/2013
@Yakk对不起,我应该真正阅读代码,我将更改我的答案来解决这个问题,因为它实际上一点也不好
0赞 aaronman 11/12/2013
@Yakk你现在可以读到我回答的结尾,IT他使用它们的方式实际上很糟糕
0赞 Bryan Chen 11/12/2013
你说的是什么意思?传递函子与传递函数指针对编译器内联有何不同?functor has the whole function
0赞 aaronman 11/12/2013
@BryanChen 我不确定我是否正确表述了它,但是当您传递函数指针时,您这样做是函数类型,当传递函子(或 lambda)时,该类型是唯一的。如果您仍然好奇,请阅读此链接int(*)(const int &)
1赞 user541686 11/12/2013 #2

C++ 并不意味着支持懒惰。在这里使用 lambda 而不是普通函数也不例外。

int f(vector<int> v)
{
    sort(begin(v), end(v));
    v.erase(unique(begin(v), end(v)), end(v));
    copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
    cout << endl;
    /* And then the function uses these helpers a few more times to justify making functions... */
}

比这更简洁、更标准、更易读

int f(vector<int> v)
{
    auto make_unique  = [](vector<int> &v)
    {
        sort(begin(v), end(v));
        auto unique_end = unique(begin(v), end(v));
        v.erase(unique_end, end(v));
    };
    auto print_vector = [](vector<int> const &v)
    {
        copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
        cout << endl;
    };

   make_unique (v);
   print_vector(v);
   /* And then the function uses these helpers a few more times to justify making functions... */
}

作为一般的经验法则,当你的代码本身并不晦涩难懂时(并且不是晦涩难懂的,它们是标准的 C++ 算法),添加更多的代码只会让情况变得更糟。erasesortcopy

此外:没有理由在这么多地方限制 to 的类型。
在类型无关紧要的地方使用类型推断。
vvector<int>

如果你觉得它应该是一个单独的函数,那么就让它完全成为一个单独的函数。
不要把lambdas当作懒惰的拐杖。

评论

0赞 aaronman 11/12/2013
我同意你的大部分答案,但是使用 lambda 怎么会懒惰呢?此外,不要懒惰是编程的核心,少打字,完成更多的工作,尽可能简单地编写代码
3赞 user541686 11/12/2013
@aaronman:它很懒惰,因为它在尖叫“我认为这应该是一个单独的函数或函子,但我不想真正让它成为一个函数,所以我就把它作为一个 lambda 留在这里”。如果它不应该是一个函数,那么它也不应该是 lambda。如果它应该是一个函数,那么它应该是一个函数。Lambda 不是用来组织代码的,而是用来支持函数式编程(即传递给其他函数的)。
0赞 aaronman 11/12/2013
完全同意,一切都很好,除了我做一个功能让我的生活更轻松,因此很懒惰
2赞 Jon Purdy 11/12/2013
@Mehrdad:对 没有隐藏的依赖关系。本地范围的定义是代码组织的一种手段,而不是懒惰的证据。make_uniquev
0赞 user541686 11/12/2013
@JonPurdy:对不起,我修复了它,我不确定我为什么要写它,谢谢。不过,关于代码组织:如果你想组织代码,这就是它的用途。只需将大括号括起来,将其分隔成一个逻辑块,远离代码的其余部分。你不需要 lambda。/* uniquify */ { ... }
7赞 Jon Purdy 11/12/2013 #3

这种本地范围函数的优点是,它们不会用“帮助程序”定义污染周围的代码,所有行为都可以限制在单个作用域内。而且,由于它们可以访问周围函数的词法范围,因此它们可用于分解行为,而无需传递许多参数。

您还可以使用它们来创建小型 DSL,用于抽象函数的机械细节,以便您以后更改它们。您可以为重复值定义常量;为什么不对代码做同样的事情呢?

举个小例子,一个状态机:

vector<int> results;
int current;
enum { NORMAL, SPECIAL } state = NORMAL;

auto input   = [&]{ return stream >> current; }
auto output  = [&](int i) { results.push_back(i); };
auto normal  = [&]{ state = NORMAL; };
auto special = [&]{ state = SPECIAL; };

while (input()) {
    switch (state) {
    case NORMAL:
        if (is_special(current)) 
          special(); 
        else output(current);
        break;
    case SPECIAL:
        if (is_normal(current)) 
            normal();
        break;
    }
}

return results;

缺点是,您可能不必要地隐藏和专门化可能对其他定义有用的泛型函数。A 或函数值得浮出并重用。uniquifyprint_vector