Functor与模板参数的区别

4
有没有使用模板参数的静态成员函数替代函数对象谓词,能够带来更好的性能优势呢?
例如,使用函数对象排序接口通常会像这样实现:
template <typename _Type, typename _Pred>
void sort (
    RandomAccessIterator first,
    RandomAccessIterator last ,
    _Pred less_than
    )
{
// actual sorting code here, calling less_than()...
}

您可以像这样做一些事情,并要求_Pred包含静态成员函数_Pred ::less_than:
template <typename _Type, typename _Pred>
void sort (
    RandomAccessIterator first,
    RandomAccessIterator last
    )
{
// actual sorting code here, calling _Pred::less_than()...
}

理论上,第一种情况可能会在堆上动态创建一个临时函数对象,而我认为第二种情况是在编译时完全评估的。我知道(比如)gcc和/或msvc很擅长优化,但在第一种情况下是否可以做到同样的程度?

另外,我并不打算重写STL排序例程或类似的东西,只是一个更一般的函数对象问题的例子...


1
为什么第一个示例会在堆上创建任何内容?它已经给出了谓词的实例? - Nikolai Fetissov
1
如果没有其他的,第二个无法推断模板参数“_Pred”的参数,这将导致调用者的语法丑陋。 - Steve Jessop
同意,“丑陋”的语法可能会出现,但是除此之外,是否有性能上的好处呢? - Darren Engwirda
你的程序有问题,Darren。你需要小心听谁的话。 - Edward Strange
@Noah - 这绝对是适得其反的,我猜你证明了自己的观点… - Darren Engwirda
显示剩余2条评论
5个回答

4

sort 的正常使用不会在堆上分配任何内容,因为没有人调用 malloc 或者 new。如果你的谓词在其构造函数或比较函数中导致了对 malloc 或者 new 的调用,那么这只能怪你自己……

有可能会为类型为 _Pred 的参数使用一些栈内存(你不应该在代码中把模板参数命名为 _Pred,因为它是保留符号,但在 std::sort 的实现中可以这样命名)。但是除了必要的数据成员外,不会有其他相关的工作需要做。如果谓词没有数据成员,那么优化器将非常高兴;如果它确实有数据成员,那么静态成员函数将无法支持用户想要的操作。

只要谓词中的 operator() 不是虚函数,编译器就可以将其内联到 sort 的实例化中,如果它能够看到定义并认为这是最好的方式。当然,没有什么保证哪种方式更快,但也没有理由认为调用静态成员函数比调用非虚非静态成员函数更快或更慢,也没有理由认为这更容易或更难被内联。


1
理论上,第一种情况可能会在堆上动态创建一个临时函数对象,而我认为第二种情况是在编译时完全评估的。
第一种情况将在堆栈上创建一个临时函数对象。您是否担心 Pred::Pred() 是否会分配存储空间?如果是这样,那么您也可以担心静态函数是否因某种原因在堆上分配存储空间。
无论如何,大多数与此类惯用法一起使用的谓词函数对象都具有非常简单的构造函数,因为它们唯一的目的是调用重载的 operator (),所以编译器很可能优化掉对象构造并产生一个简单的函数调用。

0
在第一种情况下,您可以创建一个
template<class T>
struct CompareByIntProperties {
    CompareByIntProperties(vector<T::*int> props) : props_(props) {}
    bool less_than(const T& a, const T& b) const {
        for (vector<T::*int>::const_iterator it = props_.begin();
             it != props_.end(); ++it) {
            if (a.(**it) < b.(**it)) return true;
            if (a.(**it) > b.(**it)) return false;
        }
        return false;
    }
    vector<T::*int> props_;
};

这将使您能够

vector<Foo::*int> properties;
if (compare_foo) properties.push_back(&Foo::foo);
if (compare_bar) properties.push_back(&Foo::bar);
if (compare_qux) properties.push_back(&Foo::qux);
sort(container.begin(), container.end(), CompareByIntProperties<Foo>(properties));

请原谅任何语法错误,这些都没有通过编译检查。但是你懂我的意思。
在第二种情况下,因为你正在调用一个静态方法,所以你没有自由支配来自定义比较器。
我不会担心效率的问题。如果你没有访问任何非静态内容,一个好的 C++ 编译器将省略额外的对象创建/销毁,并可能内联比较器。

你不觉得“不可能”这个说法有点过于绝对了吗? - Edward Strange
@Noah Roberts:已更新。可能可以实现,但不够简洁。 - ephemient

-1

如果_Pred::less_than不是虚拟函数,则两种解决方案相同,因为编译器知道它是什么函数并且可以内联调用(如果需要)。

这是在假设我正确理解您的代码的情况下 - 真实的代码会更清晰。我假设代码1执行类似于if (less_than.compare(a, b))的操作,而代码2执行类似于if (_Pred::less_than(a, b))的操作。

编辑:我应该提到示例1将通过值传递对象,因此您将承担可能涉及的任何成本(如复制构造函数)。


也许我应该明确说明 - 没有人会将 less_than() 定义为虚函数,但我正在解释编译器能够评估并将其内联的原因。在实验室条件下,您可以在此处使用虚函数(如果可能的话),但同样,没有一个正常的人会这样做。 - EboMike

-1

如果我是你,我会停止担心通过一种方式还是另一种方式购买微纳秒的问题...而更多地担心不使用保留的名称!

在你开始担心这种垃圾之前,你还有很长的路要走。到那时候...希望你已经学会了担心这种垃圾是毫无意义的。

虽然,为了让这成为一个“答案”:都不是,你的程序格式不正确。


我敢打赌,谁曾否定这个问题的人不够了解C++,也没有费心去找出问题所在。 - Edward Strange

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接