仿函数如何维护/存储对象的状态

时间:2012-02-21 15:27:08

标签: c++ function object

我是C ++ noob学习仿函数。我有这个代码如下(注意 - 这不是我的功课,我已经过去了!)。

它在控制台上打印0 1 2 3 4 5 6 7 8 9 我没看到它是如何保持这个对象的状态(n的值)如果函数是通过值调用而不是通过引用/指针调用

修改 我想到了这里(例1),因为函数被Value调用,构造函数每次都将n初始化为零。因此它应该始终为零,然后它应该增加到1并返回1.如何打印0 1 2 3 4 5 6 7 8 9

示例1]

class g
{
public:
    g():n(0){}
    int operator()() { return n++; }
    int n;
};

;

int main()
{
    int a[10];
    g v1;
    std::generate(a, a+10, g());//This passes a functor to generate 

    //EDIT - this will print 0 1 2 3 4 5 6 7 8 9**
    std::copy(a, a+10, std::ostream_iterator<int>(std::cout, " "));

    getchar();
    return 0;
}

因为我在仿函数中使用引用变量看到了类似下面的代码来保留状态here并使用以下概念开发了一个简单的代码:

示例2]

class CountingFunctor
{
public:
    CountingFunctor() : _counter(0) {}
    int getCounter(void) const {return(_counter);}
    void operator () (Contained item) {if(item.getShouldBeCounted()) _counter++;}
private:
    int _counter;
};
#endif

//this class uses references to maintain state in the functor
class CountingFunctor
{
public:
    CountingFunctor(int &elem) : _counter(elem) {_counter=0;}
    int getCounter(void) const {return(_counter);}
    void operator () (Contained item) {if(item.getShouldBeCounted()) _counter++;}
private:
    int &_counter;
};

int main()
{
    vector<Contained> Container(10);
    Container[3].setShouldBeCounted(false);
    Container[9].setShouldBeCounted(false);
    int elem;
    CountingFunctor CountAllWhoShouldBe(elem);
    std::for_each(Container.begin(), Container.end(), CountAllWhoShouldBe);
    std::cout << CountAllWhoShouldBe.getCounter() << " items should be counted." << std::endl;

    getchar();
}

问题是

因此,仿函数可以自己维护对象的状态,即不需要任何引用变量,如示例2所示

或示例1中的代码正常工作,因为std :: generate()通过引用/指针调用仿函数?

进一步阅读材料表示赞赏。

6 个答案:

答案 0 :(得分:2)

当你致电std::generate时,它会获得自己的仿函数对象副本。一旦进入该函数,它只是反复调用它自己的对象的单个实例,因此状态保留在里面 generate调用,但不在之间{{ 1}}和来电者。

所以,将代码更改为

generate

之后g v1; std::generate(a, a+10, v1); 仍为零。在v1.n内部,它在本地副本(比如v2)上运行,它确实增加了,但无法告诉v1它。

现在,如果你想将v2的状态传达给v1,那就是当你需要在你的仿函数中使用引用时,所以v1和v2共享在调用中变异的状态。


我们可以扩展通话以更清楚地显示:

generate

现在显而易见的是,如果g v1; std::generate(a, a+10, v1); // -> generate(begin=a, end=a+10, v2=g(v1)) { while (begin != end) *begin = v2(); } // v2 just went out of scope, and took the accumulated state with it! // v1 in the caller's scope remains unchanged ,而不是一个被深度复制并在内部保持其状态的值对象,而是保持对共享状态的引用并被浅层复制,那么v1将与v2共享相同的状态,并且在通话后可以访问该状态。

事实上,我们可以编写一个简单的包装器来自动执行此操作,因此您无需为每个仿函数手动执行此操作:

v1

现在将原始代码更改为:

template <typename OriginalFunctor, typename RType>
class StatefulFunctor
{
    OriginalFunctor &fun;

public:
    StatefulFunctor() = delete;
    StatefulFunctor(OriginalFunctor &orig) : fun(orig) {}
    StatefulFunctor(StatefulFunctor const &other) : fun(other.fun) {}
    StatefulFunctor(StatefulFunctor &&other) : fun(other.fun) {}

    template <typename... Args>
    RType operator() (Args&&... args)
    {
        return fun(std::forward<Args>(args)...);
    }
};

template <typename RT, typename OF>
StatefulFunctor<OF, RT> stateful(OF &fun)
{
    return StatefulFunctor<OF, RT>(fun);
}

表示g v1; std::generate(a, a+10, stateful<int>(v1)); 将会更新。

正如Jerry Coffin指出的那样,保持状态甚至内部电话都不能得到保证,因此即使您不需要保留状态,使用状态仿函数做这样的事也是明智的。来电者。

答案 1 :(得分:0)

原因是,仿函数对象没有任何特殊的魔法,它们与其他对象不同。但是在我的示例中,我没有看到状态仿函数应该保存的内容。

首先考虑一下: 可能的generate实现是

template <typename Iterator, typename Functor>
void generate(Iterator begin, Iterator end, Functor f)
{
    for (Iterator it  = begin; it != end; ++it) {
        *it = f();
    }
}

在此示例中,仿函数仅在函数入口处复制一次,而不是代码处理局部变量f,并且它不执行任何复制。

当您的functor有memeber n时,状态会保存在其中。

答案 2 :(得分:0)

示例1正在工作,因为仿函数对象(v1)有一个成员变量(n),每次调用对象时它都会递增。

示例2的不同之处在于,仿函数对象(v1)仅更新对位于对象外部的变量的引用。

示例1是一种更好的面向对象设计,因为您创建的每个g类对象都会自行计数,而在示例2中,调用者有责任确保计数器不共享且具有至少与仿函数对象相同的寿命。

答案 3 :(得分:0)

Functors与任何其他对象完全一样 - 如果它们的成员被定义为引用,它们通过引用存储它,如果它被定义为值,它们存储一个值。您的第一个示例有效,因为std :: generate按值获取其functor参数,而不是通过引用获取,因此对您在g()表达式中创建的临时副本进行操作。

答案 4 :(得分:0)

这取决于常见的行为,但不能保证。具体来说,它依赖于generate将为每个分配的值重新调用函数对象的事实,如下所示:

template <class FwdIt, class Generator>
void generate(FwdIt first, FwdIt last, Generator gen) {
    while (first != last) {
        *first = gen();
        ++first;
    }
}

我相信标准允许这样做,但我很确定它不能保证。至少通过我对标准的阅读,它完全可以接受这个通用命令:

template <class FwdIt, class Generator>
void generate(FwdIt first, FwdIt last, Generator gen) {
    decltype(*first) holder = gen();
    while (first != last) {
        *first = holder;
        ++first;
    }
}

在这种情况下,范围中的每个项目都将分配相同的值。也就是说,这似乎是一种实现generate的非常不寻常的方式。我很确定这是允许的,但是没有看到很多的理由。

与此同时,我应该指出,有一些微小的理由可以做到这一点。第一个是效率:存储价值可能更便宜,而不是重新创建N次。

第二个是基于对标准(§25.2.6/ 1)中描述的近似(迂腐)阅读:

  

效果:调用函数对象gen并指定gen的返回值尽管[first, last)[first, first + n)范围内的所有迭代器。

考虑到措辞的方式,你可以说它基本上说你只调用gen一次,然后将一个返回值分配给范围内的所有迭代器,而不是为每个迭代器重新调用它。范围。例如,它讨论“返回值”,意味着只有一个值,而不是该范围内每个迭代器的单独返回值。

编辑:重新阅读,我认为该标准确实提供了强烈的迹象,表明第一个是有意的。再读一下,我们得到:

  

复杂性:恰好是最后一次(或n)调用gen和赋值。

如果你是故意反常的,你仍然可以从一次调用中分配所有的值,并忽略其他调用的返回,但这确实很清楚,像上面第一个那样的实现是预期的(第二个符合原样。)

答案 5 :(得分:0)

generate函数接受一个仿函数实例并一遍又一遍地调用它。所以国家保护与每个普通班级相同。我从编译器(gcc 4.5)标题中剥离(并简化)了这个:

template<typename _ForwardIterator, typename _Generator>
void
generate(_ForwardIterator __first, _ForwardIterator __last,
         _Generator __gen)
{
  // concept requirements -- ommitted for easy reading
  for (; __first != __last; ++__first)
    *__first = __gen();
}

正如您所看到的,__ gen将是您示例中的函子的一个实例。

请注意,我的编译器优化了您的第一个示例,因此未进行任何复制构建。使用命名变量时,会发生复制构造。