美文网首页Effective STL
【Effective STL(6)】仿函数、仿函数类、函数等

【Effective STL(6)】仿函数、仿函数类、函数等

作者: downdemo | 来源:发表于2018-03-15 10:40 被阅读14次

    38 把仿函数类设计为用于值传递

    • STL函数对象在函数指针之后成型,因此STL习惯传给函数和从函数返回时,函数对象是值传递的,比如for_each算法通过值传递获取和返回函数对象
    // for_each声明
    template<class InputIterator, class Function>
    Function for_each(InputIterator first, InputIterator last, Function f);
    
    • 值传递的情况并不是完全打不破的,for_each可以显式指定参数类型,下面的代码使for_each通过引用传递和返回它的仿函数
    class DoSomething : public unary_function<int, void> {
    public:
        void operator()(int x) {...}
        ...
    };
    typedef deque<int>::iterator DequeIntIter;
    deque<int> di;
    ...
    DoSomething d; // 建立一个函数对象
    ...
    for_each<DequeIntIter, DoSomething&>(di.begin(), di.end(), d);
    
    • 但STL的用户不能这样做,如果函数对象是引用传递,有些STL算法不能编译
    • 函数对象以值传递和返回,因此需要确保两点,一是函数对象应该很小避免开销,二是函数对象不能用虚函数
    • 但不是所有的仿函数都是小的、单态的。函数对象比函数优越的原因之一是仿函数可以包含需要的所有状态。为了实现多态,把虚函数放到另一个类中,给仿函数一个指向这个类的指针。比如希望实现下面这个包含多态的仿函数(这个实现是不对的,因为仿函数不能包含虚函数)
    template<typename T> // Big Polymorphic Functor Class
    class BPFC : public unary_function<T, void> {
    private:
        Widget w; // 这个类包含很多数据
        Int x;
        ```
    public:
        virtual void operator()(const T& val) const;
        ...
    };
    
    • 正确的做法是建立一个包含一个指向实现类的指针的小而单态的类,然后把所有数据和虚函数放到实现类。这个做法在许多地方提及过,参考Effective C++ item34Exceptional C++中的pimpl、设计模式中的Bridge模式
    template<typename T>
    class BPFCImpl : public unary_function<T, void> {
    private:
        Widget w; // 以前在BPFC里的所有数据现在在这里
        int x;
        ...
        virtual ~BPFCImpl();
        virtual void operator()(const T& val) const;
        friend class BPFC<T>; // 让BPFC可以访问这些数据
    };
    template<typename T>
    class BPFC : public unary_function<T, void> {
    private:
        BPFCImpl<T> *pImpl; // BPFC唯一的数据
    public:
        void operator()(const T& val) const
        {
            pImpl->operator() (val);
        }
        ...
    };
    

    39 用纯函数做判断式

    • 返回一个bool的就叫判断式
    • 纯函数是返回值只依赖于参数的函数,比如纯函数f(x, y)的返回值仅当x或y的值改变的时候才会改变
    • 仿函数类的operator()函数是一个判断式
    • 为了明白判断式函数必须是纯函数的原因,参考下面的判断式类,第三次调用时返回true,其他时候返回false,用这个类从一个vector<Widget>中除去第三个Widget
    class BadPredicate : public unary_function<Widget, bool> {
    public:
        BadPredicate(): timesCalled(0) {}
        bool operator()(const Widget&)
        {  
            return ++timesCalled == 3;
        }
    private:
        size_t timesCalled;
    };
    
    vector<Widget> vw;
    ...
    vw.erase(remove_if(vw.begin(), vw.end(), BadPredicate()), vw.end());
    
    • 看似合理,但它不仅会除去第三个元素,还会除去第六个,参考remove_if的实现
    template <typename FwdIterator, typename Predicate>
    FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p)
    {
        begin = find_if(begin, end, p);
        if (begin == end) return begin;
        else
        {
            FwdIterator next = begin;
            return remove_copy_if(++next, end, begin, p);
        }
    }
    
    • 判断式p先传给find_if,后传给remove_copy_if。find_if先接收了一个timesCalled为0的BadPredicate对象,find_if调用三次后对象返回true,然后才调用remove_copy_if,传p的另一个拷贝作为判断式,p的timesCalled成员仍为0。结果第三次remove_copy_if调用判断式也会返回true,因此remove_if最终会删除两个Widgets
    • 避免碰到这种问题的方法是把判断式类的operator()函数声明为const,防止改变状态
    class BadPredicate : public unary_function<Widget, bool> {
    public:
        bool operator()(const Widget&) const
        {
            return ++timesCalled == 3; // 错误,不能改变局部数据
        }
    };
    
    • 把operator()声明为const是必要的,但还不够充分。行为良好的operator()当然是const,但它也得是一个纯函数,纯函数没有状态
    bool anotherBadPredicate(const Widget&, const Widget&)
    {
        static int timesCalled = 0; // 错误,纯函数不能有状态
        return ++timesCalled == 3;
    }
    

    40 使仿函数类可适配

    • 在list中找到第一个符合条件的元素很简单
    list<Widget*> widgetPtrs;
    bool f(const Widget *pw);
    list<Widget*>::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(), f);
    if (i != widgetPtrs.end()) { ... }
    
    • 但如果要找第一个不符合条件的
    list<Widget*>::iterator i =
        find_if(widgetPtrs.begin(), widgetPtrs.end(), not1(f));
    
    • 这样做会编译失败,因为not1不能直接对函数使用,所以要先用ptr_fun把函数转为函数对象
    list<Widget*>::iterator i =
        find_if(widgetPtrs.begin(), widgetPtrs.end(), not1(ptr_fun(f)));
    
    • ptr_fun做的唯一的事是使一些typedef有效,四个函数适配器not1、not2、bind1st和bind2nd都需要这些typedef。这些typedef是argument_type、first_argument_type、second_argument_type和result_type
    • std::unary_function和std::binary_function提供了这些typedef,用于给仿函数类继承。仿函数类的operator()带一个实参则继承std::unary_function,带两个实参则继承是std::binary_function
    • unary_function和binary_function是模板,所以不能直接继承,必须从它们产生的类继承,而这需要指定实参类型。对于unary_function必须指定operator()的参数类型和返回类型,对于binary_function要指定operator()的两个参数类型和返回类型
    template<typename T>
    class MeetsThreshold : public std::unary_function<Widget, bool> {
    private:
        const T threshold;
    public:
        MeetsThreshold(const T& threshold);
        bool operator() (const Widget&) const;
    ...
    };
    struct WidgetNameCompare : public std::binary_function<Widget, Widget, bool> {
        bool operator()(const Widget& lhs, const Widget& rhs) const;
    };
    
    • 一般unary_function或binary_function的非指针类型都去掉了const和引用, 因此后一个operator()的实参类型传给binary_function的是Widget。现在可以直接对上面的仿函数类使用函数适配器
    list<Widget> widgets;
    ...
    list<Widget>::reverse_iterator i1 = 
        find_if(widgets.rbegin(), widgets.rend(), not1(MeetsThreshold<int>(10)));
    Widget w(构造函数实参);
    list<Widget>::iterator i2 = 
        find_if(widgets.begin(), widgets.end(), bind2nd(WidgetNameCompare(), w);
    
    • 如果仿函数类没有继承unary_function或binary_function,上述代码就不能编译,因为not1和bind2nd都只和可适配的函数对象合作

    41 了解使用ptr_fun、mem_fun和mem_fun_ref的原因

    • 如果有一个函数f和一个对象x,要位于x的成员函数之外在x上调用f,有三种不同的语法实现这个调用
    f(x); // 语法#1:f是一个非成员函数
    x.f(); // 语法#2:f是一个成员函数且x是一个对象或对象的引用
    p->f(); // 语法#3:当f是一个成员函数且p是一个对象的指针
    
    • 假设有一个测试Widget的函数和一个Widget的容器,用for_each测试容器中的每个元素
    void test(Widget& w);
    vector<Widget> vw;
    for_each(vw.begin(), vw.end(), test); // 调用#1(可以编译)
    
    • 但如果test是Widget的成员函数,编译将失败
    class Widget {
    public:
        void test(); // 失败则把*this标记为failed
    };
    for_each(vw.begin(), vw.end(), &Widget::test); // 调用#2(不能编译)
    // 对指针的容器测试
    list<Widget*> lpw;
    for_each(lpw.begin(), lpw.end(), &Widget::test); // 调用#3(不能编译)
    
    • 原因是for_each用的是语法#1,函数和函数对象使用非成员函数的语法形式调用是STL里的一个习惯
    template<typename InputIterator, typename Function>
    Function for_each(InputIterator begin, InputIterator end, Function f)
    {
        while (begin != end) f(*begin++);
    }
    
    • mem_fun和mem_fun_ref的作用就是让成员函数使用句法1调用
    // 用于不带参数的non-const成员函数的mem_fun声明
    template<typename R, typename C> // C是类,R是成员函数的返回类型
    mem_fun_t<R,C> mem_fun(R(C::*pmf)());
    
    • 返回的mem_fun_t类型的对象是一个仿函数类,容纳成员函数指针pmf,提供一个调用成员函数的operator()
    for_each(lpw.begin(), lpw.end(), mem_fun(&Widget::test)); // 可以编译
    
    • mem_fun适配语法#3到语法#1,mem_fun_ref函数适配语法#2到语法#1,只要传一个成员函数给STL组件就必须使用它们
    • for_each没有使用ptr_fun增加的typedef,所以不必使用ptr_fun,当然加了也不会有变化。如果不清楚什么时候使用ptr_fun,就在每次传递函数给STL组件时都用上,除了降低可读性并不会有其他影响
    for_each(vw.begin(), vw.end(), ptr_fun(test)); // 同调用#1
    

    42 确定less<T>表示operator<

    • 如果一个类A有x和y两个成员,用于A的operator<按x排序
    class A {
    public:
        ...
        int x() const;
        int y() const;
        ...
    };
    bool operator<(const A& lhs, const A& rhs)
    {
        return lhs.x() < rhs.x();
    }
    
    • 现在想建立一个按y排序的multiset<A>,而multiset的默认比较函数是less<A>,默认的less<A>通过调用A的operator<来工作,于是明显想到特化less<A>使其按y排序
    template<>
    struct std::less<A> : public std::binary_function<A, A, bool> {
        bool operator() (const A& lhs, const A& rhs) const
        {
            return lhs.y() < rhs.y();
        }
    }
    
    • 这种特化并不罕见,比如智能指针想让排序行为表现得像内建指针
    namespace std {
        template<typename T>
        struct less<shared_ptr<T>>
        : public binary function<boost::shared_ptr<T>, shared_ptr<T> bool> {
            bool operator()(const boost::shared_ptr<T>& a, const boost::shared_ptr<T>& b) const
            {
                return less<T*>()(a.get(),b.get());
            }
        };
    }
    
    • 但operator<是实现less的默认方式,也是我们希望less做的,没有理由时不应该让less做其他事,回到最初的例子,如果希望按y排序,应当建立一个不叫less的仿函数类用于比较
    struct yCompare : public binary_function<A, A, bool> {
        bool operator()(const A& lhs, const A& rhs) const
        {
            return lhs.y() < rhs.y();
        }
    };
     multiset<A, yCompare> a;
    

    相关文章

      网友评论

        本文标题:【Effective STL(6)】仿函数、仿函数类、函数等

        本文链接:https://www.haomeiwen.com/subject/xqbcqftx.html