【问题标题】:Functors vs. std::bind函子与 std::bind
【发布时间】:2013-07-23 12:18:36
【问题描述】:

有时我倾向于编写函子,不是为了维护函数调用之间的状态,而是因为我想捕获一些函数调用之间共享的参数。举个例子:

class SuperComplexAlgorithm
{
    public:
        SuperComplexAlgorithm( unsigned int x, unsigned int y, unsigned int z )
            : x_( x ), y_( y ), z_( z )
        {}

        unsigned int operator()( unsigned int arg ) const /* yes, const! */
        {
            return x_ * arg * arg + y_ * arg + z_;
        }

    private:
        // Lots of parameters are stored as member variables.
        unsigned int x_, y_, z_;
};

// At the call site:
SuperComplexAlgorithm a( 3, 4, 5 );
for( unsigned int i = 0; i < 100; ++i )
    do_stuff_with( a ); // or whatever

对比

unsigned int superComplexAlgorithm( unsigned int x, unsigned int y,
                                    unsigned int z, unsigned int arg )
{
    return x * arg * arg + y * arg + z;
}

// At the call site:
auto a = std::bind( superComplexAlgorithm, 3, 4, 5, std::placeholders::_1 );
for( unsigned int i = 0; i < 100; ++i )
    do_stuff_with( a );

在我看来,第一个解决方案有很多缺点:

  • xyz 所做工作的文档在不同的位置(构造函数、类定义、operator())进行拆分。
  • 大量样板代码。
  • 不太灵活。如果我想捕获不同的参数子集怎么办?

是的,我刚刚意识到 boost::bindstd::bind 有多么有用。现在,在我开始在我的很多代码中使用它之前,我的问题是:在任何情况下,我应该考虑在普通函数中使用手写无状态仿函数来绑定参数吗?

【问题讨论】:

  • 最重要的是你应该考虑 lambdas
  • @stijn:我不想在调用现场定义superComplexAlgorithm,因为它超级复杂,用在很多地方,需要测试和文档等。我不能看看 lambda 在这种情况下如何发挥作用,尽管我在其他情况下也经常使用它们。
  • @MarkusMayr​​,您可以用 lambda 替换 std::bind。你写的不是std::bind( superComplexAlgorithm, 3, 4, 5, std::placeholders::_1 ),而是[](int x) { return superComplexAlgorithm(3, 4, 5, x); }
  • 自从我可以使用 lambdas 以来,我没有使用过 bind 或重载 operator() 一次。所以不,我无法想象这样的情况:) 将代码放入 lambda 或编写一个函数并从 lambda 调用它。
  • 我想指出这个模式的“官方”名称是Currying

标签: c++ c++11 boost-bind


【解决方案1】:

lambda 解决方案将是惯用的 C++11 方式:

auto a = [&]( int x ){ return superComplexAlgorithm( 3, 4, 5, x ); };
for( unsigned int i = 0; i < 100; ++i )
  do_stuff_with( a );

使用手写函子的好处是您可以:

  1. 将参数移动到捕获中,或对其进行转换(您可以在 C++1y 中使用 lambda,但目前还不行——这也可以使用 bind
  2. 它有一个非匿名类型(所以你可以不使用auto 来谈论类型)——bind 也是如此,但它也有一个非匿名有意义 键入您可以在没有decltypeing 整个表达式的情况下输入! C++1y 再次使 bind/lambda 变得更好。
  3. 您可以完美地转发剩余的参数(您可以在 C++1y 中使用 lambda 进行此操作,而 bind 会这样做)
  4. 您可以随意处理捕获的数据的存储方式(在指针中?智能指针?),而不会弄乱创建实例的代码。

您还可以使用手写函子做一些非常强大的事情,这些事情是 bind 和 lambdas 无法做到的,例如将多个函数的重载集包装到一个对象中(可能共享名称也可能不共享名称),但是这种极端情况不会经常出现。

【讨论】:

  • 为什么你的 lambda 中有一个捕获子句?除非您确实需要捕获某些内容,否则不应使用捕获。
  • 如果根本没有捕获,为什么要通过引用捕获?
  • @sebastianredl 它使它类似于绑定:通常您绑定的值不是编译时常量,或者您不会打扰bind 或 lambda:一个普通的旧函数就可以了。如果我错了,运行时成本为零,所以我把它包括在内。
  • 如果您想模仿bind,请使用复制捕获。
  • +1 @SebastianRedl:另外,如果你正在捕获成员,请注意
【解决方案2】:

在什么情况下我应该考虑在普通函数中使用手写无状态函子而不是绑定参数?

来自cmets:

我不想在调用现场定义 superComplexAlgorithm,因为它超级复杂,用在很多地方,需要测试和文档等。

如果superComplexAlgorithm 的实现需要一堆代码,而您最终将其拆分为需要大量参数的不同函数,那么最好使用一个在内部实现中提供共享状态的类职能。除了那个极端情况,std::bind 的行为将等同于您在问题中提供的函子。

一些注意事项,因为您提到 boost::bindstd::bind 作为替代品。您可能想测试您的实现是做什么的。例如,对于 boost 1.35(古代),bind 操作将制作每个参数的 4 个副本,VS2010 将在 std::bind 中制作 7 个副本,尽管 gcc 4.7 中的 std::bind 只做了 1 个。如果参数很小,那将不会t 弥补高成本,但如果您正在执行操作或者您的对象复制成本很高......只需测量即可。

关于 lambda,如果性能是一个问题,还要测量它的行为方式。它应该制作一个副本。如果性能不是问题,那么 lambda 对捕获的内容就不那么明确了(用户需要阅读 lambda 的实现来弄清楚这一点),即使查看代码也可能不是那么明显。

【讨论】:

    猜你喜欢
    • 2014-03-25
    • 2014-01-09
    • 1970-01-01
    • 1970-01-01
    • 2016-11-28
    • 1970-01-01
    • 2019-01-18
    • 2012-10-28
    • 1970-01-01
    相关资源
    最近更新 更多