4

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

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 );

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

  • x, y,做什么的文档z在不同的地方 (构造函数, 类定义, operator()) 进行拆分。
  • 大量的样板代码。
  • 不太灵活。如果我想捕获不同的参数子集怎么办?

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

4

2 回答 2

9

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,但它也有一个非匿名的有意义的类型,你可以在不decltype使用整个表达式的情况下获得!C++1y 再次使bind/lambda 变得更好。
  3. 您可以对剩余参数进行完美转发(您可以在 C++1y 中使用 lambda 执行此操作,并bind执行此操作)
  4. 您可以处理捕获的数据的存储方式(在指针中?智能指针?),而不会弄乱创建实例的代码。

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

于 2013-07-23T12:35:45.193 回答
4

在任何情况下我应该考虑在普通函数中使用手写的无状态函子来绑定参数?

从评论:

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

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

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

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

于 2013-07-23T13:28:37.470 回答