【问题标题】:When are stateless class functors useful in place of a c style function?什么时候无状态类函子可以代替 c 风格的函数?
【发布时间】:2023-03-10 17:25:01
【问题描述】:

我在 SO 上找到了一些很好的仿函数示例,例如 this one,所有令人信服的示例似乎都在定义 operator() 的类中使用了状态。

我在一本书中遇到了一个例子,它定义了没有状态的函数调用运算符,我不禁觉得这是一个尴尬的用法,而且普通样式的函数指针会比使用 @ 更好987654323@ 在这里的各个方面 - 更少的代码,更少的变量(你必须实例化比较器),由于实例化,它可能更有效,并且没有失去意义或封装(因为它只是一个函数)。

我知道std::sort 可以让您在operator() 类和函数之间进行选择,但由于上述逻辑,我一直只使用函数。

一个类可能被首选的原因是什么?

以下是示例(转述):

class Point2D {
   //.. accessors, constructors
   int x,y;
};
class HorizComp {
public:
   bool operator()(const Point2D& p, const Point2D& q) const
   { return p.getX() < q.getX(); }
};

class VertComp {
public:
   bool operator()(const Point2D& p, const Point2D& q) const
   { return p.getY() < q.getY(); }
};

template <typename E, typename C>
void printSmaller(const E& p, const E& q, const C& isLess) {
   cout << (isLess(p, q) ? p : q) << endl; // print the smaller of p and q
}
//...
// usage in some function:
Point2D p(1.2, 3.2), q(1.5, 9.2);
HorizComp horizComp;
VertComp vorizComp;
printSmaller(p, q, horizComp);
printSmaller(p, q, vorizComp);

【问题讨论】:

    标签: c++ operator-overloading functor


    【解决方案1】:

    典型的原因是当你这样做时:

    bool less_than(const Point&, const Point&);
    // ...
    std::sort(..., &less_than);
    

    谓词的模板参数如下:

    bool(const Point&,const Point&)
    

    由于 sort 函数接收到一个函数指针,编译器更难内联 std::sort() 内部的谓词使用。发生这种情况是因为您可以拥有另一个功能

    bool greater_than(const Point&, const Point&);
    

    具有完全相同的类型,这意味着std::sort() instatiation 将在两个谓词之间共享。 (请记住,我说过它使内联更加困难,并非不可能)。

    相反,当你这样做时:

    struct less_than {
        bool operator()(const Point&, const Point&) const;
    };
    // ...
    std::sort(..., less_than());
    
    
    struct greater_than {
        bool operator()(const Point&, const Point&) const;
    };
    // ...
    std::sort(..., greater_than());
    

    编译器为每个谓词生成std::sort() 的唯一模板实例化,从而更容易内联谓词的定义。

    【讨论】:

    • 酷,我没想到。我发现了一些显示内联性能的博客文章:codeforthought.blogspot.com/2011/07/…
    • 我必须承认我认为这是一个编译器问题。我已经看到了与内联类似的问题,因此编译器无法对调用进行去虚拟化。在我看来,这是一个增强的常量传播应该实现的(只要在这种情况下,less_than 的定义是可见的)。
    • @MatthieuM.:这绝对是编译器问题。这就是我使用“更难”、“更容易”等术语的原因。编译器不会为两个谓词生成单独的实例化作为函数并仍然内联它们的主体,这没有根本原因。只是,出于实际原因,编译器实现者可能(还)没有为这种情况制定特殊情况。
    • 我同意你的观点,只是就我而言,传播地址/对函数的引用类似于(实际上)传播文字值。我已经看到 Clang 痛苦地去虚拟化函数调用,但它并没有像它可能的那样工作,因为 LLVM 后端正在执行内联,这暴露了更多的机会并且不执行它本身(可能是 vtables 表示的问题。 ..)。可惜了。
    【解决方案2】:

    一个原因是运行时效率。如果您将指针传递给函数,编译器必须非常聪明地为该函数内联生成代码。传递定义operator() 的对象使编译器更加更容易生成内联代码。尤其是对于像排序这样的事情,这可以大大提高速度。

    在 C++11 中,使用类的另一个原因是为了方便——您可以使用 lambda 表达式来定义类。

    【讨论】:

      【解决方案3】:

      其他人对编译器内联函子的能力提出了很好的观点。函子对象与函数指针相比​​的另一个可能优势是灵活性。仿函数可能是一个模板,可能是一个派生类,可能它具有运行时配置(即使在调用 operator() 时是无状态的等等。

      【讨论】:

        【解决方案4】:

        另一个原因是有时一个比较函数是不够的。假设我们有一个指针向量:

        struct X { string name; };
        vector<shared_ptr<X>> v;
        

        现在如果我们想按name 对向量进行排序,我们必须为sort 函数定义自己的谓词:

        struct Cmp1
        {
            bool operator()(const shared_ptr<X>& left, const shared_ptr<X>& right) const
            { return left->name < right->name; }
        };
        

        这很酷,但是当我们需要查找具有特定名称的对象时该怎么办?要使用equal_range,谓词需要有两个不同的比较函数:

        struct Cmp2
        {
            bool operator()(const shared_ptr<X>& left, const string& right) const
            { return left->name < right; }
        
            bool operator()(const string& left, const shared_ptr<X>& right) const
            { return left < right->name; }
        };
        

        这允许我们使用string 名称对象调用equal_range

        equal_range(v.begin(), v.end(), name, Cmp2())
        

        【讨论】:

          【解决方案5】:

          在定义时不知道仿函数参数状态的模板库中,类类型的参数提供比非实例函数指针更通用的接口。 STL 是一个很好的例子,其中有状态或无状态的谓词和函子可以用作类和函数的参数。 如果模板编程不是计划的一部分,则函数指针优于无状态函子类;单个函数实例可以接受特定签名的所有函数指针,并且代码大小最小化。但万一将来库扩展的可能性很小,函子使它更通用。

          【讨论】:

            猜你喜欢
            • 2016-04-19
            • 1970-01-01
            • 2020-07-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-15
            相关资源
            最近更新 更多