仿函数续

来源:互联网 发布:网络兼职主播是真的吗 编辑:程序博客网 时间:2024/05/16 08:52
不要试图获得仿函数的状态,或者讲不要试图通过仿函数来确定仿函数的某个值或状态


试图从运用了仿函数的算法中获得数据或者结果的方式有两种,分别是显式写出传递的类型参数,使用引用。如下面的:

    list<int> coll;

    IntSequence seq(3);
   
    generate_n<back_insert_iterator<list<int> >,int,IntSequence&>(back_inserter(coll),4,seq);

或者使用for_each算法,返回一个仿函数的copy~:

    int array[10]={0,1,2,3,4,5,6,7,8,9};

    for_each(array,array+9,add1);

这是原生指针的情况。

    这两种情况,第二种限定了算法。

    对于第一种,有一个很大的问题,那就是不同编译环境其STL库的对应算法实现是不同的!这造成第一种情况在有些IDE中无法实现!

    #include <iostream>
    #include <string>
    #include <math.h>
    #include <stdlib.h>
    #include <fstream>
    #include <algorithm>
    #include <iterator>
    #include <list>
    using namespace std;

class IntSequence
{
private:
    int value;
   
public:
    IntSequence(int initialValue):value(initialValue)
    {

    }
    int operator()()
    {
        cout<<value+1<<endl;
        return value++;
    }

};


template<class T>
inline void PRINT_ELEM(const T&col,const char *op = "")
{
    T::const_iterator pos;            //使用const迭代器,表示不能修改元素
    std::cout<<op;
    for(pos = col.begin();pos!=col.end();++pos)
    {
        std::cout<<*pos<<' ';
    }
    std::cout<<endl;
}


int main()
{
    list<int> coll;

    IntSequence seq(3);
   
    generate_n<back_insert_iterator<list<int> >,int,IntSequence&>(back_inserter(coll),4,seq); //传引用方式
    PRINT_ELEM(coll,"print:\n");
    //generate_n(back_inserter(coll),4,seq);
    generate_n<back_insert_iterator<list<int> >,int,IntSequence&>(back_inserter(coll),4,seq);
    PRINT_ELEM(coll);
    system("pause");


    return 0;
}
这个情况下,你会发现在VS2010VS2012上,这个seq并没有按照我们想的那样,按引用传递,我们可以重用修改后的seq。

但是这个情况在codeblock上就完全不一样了!

这是为什么呢。如果我们自己实现IntSequence的拷贝构造函数,你就会发现如下问题

#include <iostream>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <fstream>
#include <algorithm>
#include <iterator>
#include <list>
using namespace std;

class IntSequence
{
private:
    int value;
   
public:
    IntSequence(int initialValue):value(initialValue)
    {

    }
    int operator()()
    {
        cout<<value+1<<endl;
        return value++;
    }
    IntSequence& operator=(const IntSequence &t)
    {
        cout<<"123";
        value = t.value;
    }
    IntSequence(IntSequence &t)
    {
        cout<<"456"<<endl;
        value = t.value;
        cout<<t.value<<endl;
    }
};
template<class T>
inline void PRINT_ELEM(const T&col,const char *op = "")
{
    T::const_iterator pos;            //使用const迭代器,表示不能修改元素
    std::cout<<op;
    for(pos = col.begin();pos!=col.end();++pos)
    {
        std::cout<<*pos<<' ';
    }
    std::cout<<endl;
}


int main()
{
    list<int> coll;

    IntSequence seq(3);
   
    generate_n<back_insert_iterator<list<int> >,int,IntSequence&>(back_inserter(coll),4,seq); //传引用方式
    PRINT_ELEM(coll,"print:\n");
    //generate_n(back_inserter(coll),4,seq);
    generate_n<back_insert_iterator<list<int> >,int,IntSequence&>(back_inserter(coll),4,seq);
    PRINT_ELEM(coll);
    system("pause");
 

    return 0;
}

你会发现,在这里多次调用了拷贝构造函数,这是为什么呢。
而当这段代码在codeblock上运行时,又没调用拷贝构造函数。

这是因为vs和codeblock上面的对STL的实现不同

首先,来看拷贝构造函数的作用
 
我们写这么一个类A
class A
{

public:
    A(A&t)
    {
        cout<<"nidaye"<<endl;
    }
    A(int t):a(t){}
    int a;
};
template <class T>
void func1(T t)
{
    t.a+=2;
}
template <class T>
void func(T t)
{
    t.a+=2;
    //return func1(t);
    //return func1<T&>(t);
}
int main()
{
  
    A t(3);
    func<A&>(t);
    cout<<t.a;
    return 0;
}
重要的代码我变色示意 ,首先是绿色的代码,如果我们不显式写出传递的是A&类型,结果是返回3和nidaye。
即调用了一次拷贝构造函数,因为是值传递

如果我们显式写出传递的是A&类型,结果就是5,因为我们是按引用传递

对于红色的代码,如果我们不在func内修改,而是调用func1来修改,这时候返回的结果就是3和nidaye
这是为什么呢

因为调用内部函数的时候是传递的是值传递,而并没有把引用传递,在调用func1的时候,会调用拷贝构造函数

那么如果想多次传递仍保证是值传递,那么可以写成return func1<T&>(t),这样可以显式的传递一个引用

另一种方式,就是传递类型的时候不用显式传递,但是func1的参数是&t即T*类型,然后需要修改蓝色部分为t->a+=2

也可以避免值传递。

接下来来看VS和codeblock对

以generate_n算法为例。

这是codeblock的实现
  template<typename _OutputIterator, typename _Size, typename _Generator>
    _OutputIterator
    generate_n(_OutputIterator __first, _Size __n, _Generator __gen)
    {
      // concept requirements
      __glibcxx_function_requires(_OutputIteratorConcept<_OutputIterator,
            // "the type returned by a _Generator"
            __typeof__(__gen())>)

      for (__decltype(__n + 0) __niter = __n;
       __niter > 0; --__niter, ++__first)
    *__first = __gen();
      return __first;
    }
可以看到,传入的引用立刻被使用为__gen()用于产生一个插入值,然后通过*来获得back_inserter本身,然后通过重载的=运算符中实现插入
 

在VS中的generate_n的实现,首先调用
template<class _OutIt,
    class _Diff,
    class _Fn0> inline
    _OutIt generate_n(_OutIt _Dest, _Diff _Count, _Fn0 _Func)
    {    // replace [_Dest, _Dest + _Count) with _Func()
    _DEBUG_POINTER(_Dest);
    _DEBUG_POINTER(_Func);
    return (_Generate_n(_Dest, _Count, _Func,
        _Is_checked(_Dest)));
    }
然后调用
template<class _OutIt,
    class _Diff,
    class _Fn0> inline
    _OutIt _Generate_n(_OutIt _Dest, _Diff _Count, _Fn0 _Func,
        true_type)
    {    // replace [_Dest, _Dest + _Count) with _Func(), checked dest
    return (_Generate_n1(_Dest, _Count, _Func,
        _Iter_cat(_Dest)));
    }

然后调用
template<class _OutIt,
    class _Diff,
    class _Fn0> inline
    _OutIt _Generate_n1(_OutIt _Dest, _Diff _Count, _Fn0 _Func,
        output_iterator_tag)
    {    // replace [_Dest, _Dest + _Count), arbitrary iterators
    return (_Generate_n(_Dest, _Count, _Func));
    }
然后调用
template<class _OutIt,
    class _Diff,
    class _Fn0> inline
    _OutIt _Generate_n(_OutIt _Dest, _Diff _Count, _Fn0 _Func)
    {    // replace [_Dest, _Dest + _Count) with _Func()
    for (; 0 < _Count; --_Count, ++_Dest)
        *_Dest = _Func();
    return (_Dest);
    }

一共3次间接调用,并且没有显式传递引用参数,这表示即使开始我们使用显式的引用参数传递,在3次间接调用之后,也会调用3次拷贝构造函数,并且最终的实现使用的仅仅是一个原传入对象的副本。所以我们无法获得VS中仿函数的状态变化。


例子2:

#include <iostream>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <fstream>
#include <algorithm>
#include <iterator>
#include <list>
using namespace std;

class A
{

public:
    A(A&t)
    {
        cout<<"nidaye"<<endl;
    }
    A(int t):a(t){}
    int a;
};

template <class T>
void func1(T t)
{
    t.a+=2;
    //cout<<t.a<<endl;
}

template <class T>
void func(T t)
{
    t.a+=2;
    //cout<<t.a<<endl;
}

template <class T>
void func2(T t)
{
    t.a+=2;
    return func1(t);
}

template <class T>
void func3(T t)
{
    t.a+=2;
    return func1<T&>(t);
}


int main()
{

    A t(3);
    //func(t);
    //func<A>(t);  //func(t)和func<A>(t)参数类型非引用,调用时会调用拷贝构造函数,输出“nidaye”
    //cout<<t.a;   // 由于参数为非引用类型,即值传递,实参t的值未发生改变, 输出为 3;

    //func<A&>(t);  //参数类型为引用,调用时不会调用拷贝构造函数
    //cout<<t.a;    // 由于参数为引用类型,func中对参数的改变就是对t的改变,t.a 加2,输出为5;

    //func2<A&>(t);    // 由于参数为引用类型,func中对参数的改变就是对t的改变,t.a 加2 = 5;接下来 return func1(t); 由于func1为值传递,会创建t的一个临时拷贝对象t1,调用拷贝构造函数,输出“nidaye”,对临时对象t1.a+2 ;但是func1函数不会改变实参t
    // 因此可以看到fun2(A&)(t),这里的引用只作用到void func2(T t)参数列表的T类型上,并不是作用于参数t上,因此在fun2()中以t为参数调用fun1时,如果fun1不指明为<A&>时(对比如fun3中),此时t为值传递
    //cout<<t.a;

    //func3<A&>(t);      //通过fun2和fun3的比较,可以得出当模板函数中调用另一模板函数时,即使参数为同一个对象,外层模板参数的引用类型不会传给内层调用的参数,必须为每一个模板类型明确指出,如fun3中所示
    //cout<<t.a;

    system("pause");
    return 0;
}


0 0