我想说我要撰写函数,
例如processResult
和sendResult
,但我不能将它们链接起来因为processResult
每次调用{sendResult
可能需要调用processResult
0,1,2或n次1}}。
在C ++ 11中执行此操作的正确方法是什么?
我想到了两个解决方案:
1)给第一个函数一个std::function
param(并为其指定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)
答案 0 :(得分:4)
可组合编程或基于堆栈的编程模型可能有意义。
每个函数都使用一堆参数并返回相同的参数。这些堆栈可以是vector
或tuple
或生成器/通用范围。确定输入参数数量的一些方法可能很有用。
在这种情况下,您的生成器会生成一个“本地”堆栈,然后将该堆栈抛给消费者,直到完成为止。
如果你想让这些操作交错,你可以让生成器函数返回一个生成器函数或生成迭代器的对,它们懒洋洋地生成你的元素,或者你传递一个输出堆栈,其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};
};
};
如果您更喜欢迭代器,将Generator<T>
转换为生成迭代器是您只需编写一次,并且适用于所有Generator<T>
。编写基于Generator<>
的代码比编写基于迭代器的代码更容易。加Generator
和Pipe
很容易链接:
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
“更加专业化。”
这意味着任何std::function< optional<T>()>
都被视为生成器,不正确。可能struct generator_finished {}; variant< generator_finished, T >
是一种比optional
更好的方法,因为当您不是生成器时在返回值的变体中使用generator_finished
似乎是不礼貌的。
答案 1 :(得分:0)
如果您只是需要多次调用另一个函数,可以在processResult
内调用一个sendResult
(反之亦然,如果您转发 - 宣布需要什么。
int processResult() { /* blah blah */ }
void sendResult() {
while(needed) {
if(needed)
processResult();
}
}
答案 2 :(得分:0)
这对我来说实际上看起来不像是函数组合,因为如果是这样的话,你就可以简单地链接它们。根据我的阅读,您需要processResult
应该能够调用一些外部方法来进一步处理数据。在这种情况下,我会考虑这两个解决方案:
std::function
传递给processResult
。这允许您传递更多对象:函数,仿函数对象甚至lambda到该方法。将这两种方法更改为所谓的处理器类。如果它们是函数或方法,您将能够更灵活地使用它们,因此如果程序的复杂性增加,这个应该更适合您的需求。
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);
}
};
在前面的示例中,Sender还可以获得另一个执行实际发送的类,因此您可以“链接”更多依赖对象。
很大程度上取决于你的程序架构而且我担心,如果没有更详细的信息,没有人能够进一步帮助你。
答案 3 :(得分:0)
可能你就是把你的sendResult放在一堂课上。在进程Result的类中定义该类的对象。然后,您可以使用此对象从处理结果中多次发送sendResult。