1

假设我想编写函数,比如processResultand sendResult,但我不能只是链接它们,因为每次processResult调用. 在 C++11 中执行此操作的正确方法是什么?sendResultprocessResult

我想到了两个解决方案:
1)给第一个函数一个std::function参数(并分配processResult给它),所以它可以在需要时调用它。

2)(不喜欢这个似乎太复杂)线程安全队列,将函数放在2个线程中......

@requests 例如:

input 1,2,3
calls of functions:
processResult(1)
//nothing
processResult(2)
//calls:
sendResult(10)
sendResult(20)
sendResult(50)
processREsult(3)
//calls
sendREsult(55)
4

4 回答 4

4

可组合编程或基于堆栈的编程模型可能是有意义的。

每个函数都接受一堆参数并返回相同的参数。这些堆栈可以是vectortuple或生成器/通用范围。确定输入参数数量的一些方法可能是有用的。

在这种情况下,您的生成器会创建一个“本地”堆栈,然后将该堆栈扔给消费者,直到完成。

如果您希望这些操作交错,您可以让生产者函数返回一个生成器函数或一对生成迭代器,它们会延迟生成您的元素,或者您将其传递给输出堆栈,其push方法将其传递给消费者函数(即,传递生产者的消费者功能)。

生成器解决方案的好处是它不会用 crud 填充您的执行堆栈,并且您可以更好地控制接下来发生的事情。缺点是需要显式存储生产者状态,而不是在堆栈中,并且必须对生产者进行合理的大量修改。使用 lambdas 并没有那么糟糕,因为您可以将循环的下一次迭代存储为闭包,但这仍然很棘手。

这是一个简单的生成器:

using boost::optional; // or std::optional in C++14
using boost::none_t;
template<typename T>
using Generator = std::function< optional<T>() >;

Generator<int> xrange( int min, int max, int step=1 ) {
  int next = min;
  return [=]()->optional<int> mutable
  {
    if (next > max) return {none_t};
    int retval = next;
    next += step;
    return {retval};
  };
};

如果您更喜欢迭代器,将 aGenerator<T>转换为生成迭代器只需编写一次,它适用于所有Generator<T>. 并且编写Generator<>基于代码的代码比编写基于迭代器的代码更容易。加Generators 和Pipes 很容易链接:

template<typename T, typename U>
using Pipe = std::function< optional<T>(U) >;

template<typename A, typename B, typename C>
auto Compose( Pipe<A, B> second, Generator<C> first )
  -> decltype( second( std::move(*first()) ) )
{
  return [=]()->optional<A> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
template<typename A, typename B, typename C, typename D>
auto Compose( Pipe<A, B> second, Pipe<C, D> first )
  -> decltype( second( std::move(*first( std::declval<D>() ) ) ) )
{
  return [=](C c)->optional<A> mutable {
    optional<B> b = first(c);
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
// until C++14, when we get auto deduction of non-lambda return types:
#define RETURNS(x) -> declval(x) { return {x}; }
template<typename A, typename B, typename C>
auto operator|( Generator<A> first, Pipe<B,C> second )
  RETURNS( Compose(second, first) )

template<typename A, typename B, typename C, typename D>
auto operator|( Pipe<A, B> first, Pipe<C,D> second ) {
  RETURNS( Compose( second, first ) )

然后我们像这样滥用:

struct empty {}; // easier to pass through pipes than void
template<typename T>
void sendEverything( Pipe<empty, T> sender, Generator<T> producer ) {
  Generator<empty> composed = producer | sender;
  while (composed()) {}
}

生产者愉快地生产数据,每个数据都发送给发送者,然后再次调用生产者。发送者甚至可以通过返回一个none_t.

更高级的工作,我们将能够拥有表示一对多和多对一关系的管道。

(代码尚未测试,因此可能包含编译器错误)

template<typename Out, typename In>
using OneToManyPipe = Pipe< Generator<Out>, In >;
template<typename Out, typename In>
using ManyToOnePipe = Pipe< Out, Generator<In> >;
template<typename Out, typename In>
using ManyToManyPipe = Pipe< Generator<Out>, Generator<In> >;

template<typename Out, typename A, typename B>
auto Compose( OneToManyPipe< Out, A > second, Generator<B> first )
  -> decltype( second( std::move(*first()) ) )
{
  auto sub_gen = [=]()->optional<Generator<Out>> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
  optional<Generator<Out>> sub = []()->optional<Out> { return {none_t}; };
  return [sub_gen,sub]()->optional<Out> mutable {
    for(;;) {
      if (!sub)
        return {none_t};
      optional<Out> retval = (*sub)();
      if (retval)
        return retval;
      sub = sub_gen();
    }
  }
}
template<typename Out, typename A, typename B, typename C>
auto Compose( OneToManyPipe< Out, A > second, OneToManyPipe<B, C> first )
  -> OneToManyPipe< decltype( *second( std::move(*first()) ) ), C >;
// etc

可能boost已经在某个地方这样做了。:)

这种方法的一个缺点是最终重载的运算符会变得模棱两可。特别是,OneToMany管道和OneToOne管道之间的区别在于,第二个是第一个的子类型。我想这optional<T>会使OneToMany“更专业”。

这确实意味着 anystd::function< optional<T>()>被视为生成器,这是不对的。可能 astruct generator_finished {}; variant< generator_finished, T >是比 a 更好的方法optional,因为generator_finished当您不是生成器时在返回值的变体中使用似乎不礼貌。

于 2013-06-05T11:44:32.353 回答
0

This does not actually look like function composition to me, because if it was, you would be able to simply chain them. From what I read, you require, that processResult should be able to call some external methods further processing the data. In such case, I would think about these two solutions:

  • Pass std::function to processResult. This allows you to pass more objects: a function, functor object or even a lambda into that method.
  • Create an interface, which provides all required operations and pass it to processResult.
  • Change these two methods into so-called processor classes. You would be able to use them more flexibly than if they are functions or methods, so this one should suit your needs better if complexity of your program increases.

    class VectorSender : IDataSender
    {
    private:
        std::vector<Data> & target;
    
    public:
        VectorSender(std::vector<Data> & newTarget)
            : target(newTarget)
        {
        }
    
        // Implementation of IDataSender
        void Send(Data data)
        {
            target.push_back(data);
        }
    };
    
    class Processor : IDataProcessor
    {
    private:
        sender : IDataSender;
    
    public:
        Processor(IDataSender newSender)
            : sender(newSender)
        {
        }
    
        // Implementation of IDataProcessor
        void Process()
        {
             // Do some processing
             if (sendData)
                  sender.Send(someData);
        }
    };
    

In the previous example, Sender may also get another class performing the actual sending, so you are able to "chain" more dependent objects.

Much depends on your program's architecture and I'm afraid, that no one will be able to help you further without some more detailed information about it.

于 2013-06-05T11:10:01.107 回答
0

如果您只需要多次从另一个函数调用一个函数,您可以processResult在内部调用一个函数sendResult(反之亦然,前提是您前向声明需要什么)。

int processResult() { /* blah blah */ }

void sendResult() { 
    while(needed) {
        if(needed)
           processResult();
    }
}
于 2013-06-05T11:07:34.070 回答
0

可能你只是把你的 sendResult 放在一个类中。在流程 Result 的类中定义该类的对象。然后,您可以使用此对象从处理结果中多次发送结果。

于 2013-06-05T11:13:10.460 回答