我正在创建一个节点系统(类似于UE4或Blender's Cycles),在其中可以创建不同类型的节点并在以后使用它们。目前,我有2类具有以下输出函数的节点:
class InputInt
{
public:
int output()
{
int x;
std::cin>>x;
return x;
}
};
class RandomInt
{
public:
int rand10()
{
int x;
x = rand()%10;
return x;
}
int rand100()
{
int x;
x = rand()%100;
return x;
}
};
我什么也不传递给这些节点。现在,我想创建一个节点,该节点接受上述类之一的对象并输出函数。这是我仅使用InputInt
节点的实现方式:
class MultiplyBy2
{
typedef int (InputInt::*func)();
func input_func;
InputInt *obj;
public:
MultiplyBy2(InputInt *object, func i): obj(object), input_func(i) {}
int output()
{
return (obj->*input_func)()*2;
}
};
有了这个,我可以在MultiplyBy2
中创建和使用main()
的对象,它可以完美地工作。
int main()
{
InputInt input;
MultiplyBy2 multi(&input, input.output);
std::cout<<multi.output()<<std::endl;
}
对于RandomInt
对象显然不起作用,因为我必须将*InputInt
对象传递给MultiplyBy2
对象。有没有一种方法可以使MultiplyBy2
带有其输出函数的任何类型的对象,例如。这样吗?
int main()
{
RandomInt random;
MultiplyBy2 multi2(&random, random.rand10);
std::cout<<multi2.output()<<std::endl;
}
答案 0 :(得分:4)
另一种方法,将公共基类与虚拟方法一起使用:
#include <iostream>
struct IntOp {
virtual int get() = 0;
};
struct ConstInt: IntOp {
int n;
explicit ConstInt(int n): n(n) { }
virtual int get() override { return n; }
};
struct MultiplyIntInt: IntOp {
IntOp *pArg1, *pArg2;
MultiplyIntInt(IntOp *pArg1, IntOp *pArg2): pArg1(pArg1), pArg2(pArg2) { }
virtual int get() override { return pArg1->get() * pArg2->get(); }
};
int main()
{
ConstInt i3(3), i4(4);
MultiplyIntInt i3muli4(&i3, &i4);
std::cout << i3.get() << " * " << i4.get() << " = " << i3muli4.get() << '\n';
return 0;
}
输出:
3 * 4 = 12
正如我在与OP进行的回答后谈话中提到std::function
时一样,我对这个想法有些费解,得到了:
#include <iostream>
#include <functional>
struct MultiplyIntInt {
std::function<int()> op1, op2;
MultiplyIntInt(std::function<int()> op1, std::function<int()> op2): op1(op1), op2(op2) { }
int get() { return op1() * op2(); }
};
int main()
{
auto const3 = []() -> int { return 3; };
auto const4 = []() -> int { return 4; };
auto rand100 = []() -> int { return rand() % 100; };
MultiplyIntInt i3muli4(const3, const4);
MultiplyIntInt i3muli4mulRnd(
[&]() -> int { return i3muli4.get(); }, rand100);
for (int i = 1; i <= 10; ++i) {
std::cout << i << ".: 3 * 4 * rand() = "
<< i3muli4mulRnd.get() << '\n';
}
return 0;
}
输出:
1.: 3 * 4 * rand() = 996
2.: 3 * 4 * rand() = 1032
3.: 3 * 4 * rand() = 924
4.: 3 * 4 * rand() = 180
5.: 3 * 4 * rand() = 1116
6.: 3 * 4 * rand() = 420
7.: 3 * 4 * rand() = 1032
8.: 3 * 4 * rand() = 1104
9.: 3 * 4 * rand() = 588
10.: 3 * 4 * rand() = 252
通过std::function<>
,可以将类方法,独立功能甚至lambda结合使用。因此,节点不再需要基类。实际上,甚至不再需要节点(显式)(如果不将独立功能或lambda视为“节点”)。
我必须承认图形数据流编程是我在大学的最后工作的主题(尽管这是很久以前的事情了)。我记得我很杰出
以上两个示例都是需求驱动的执行。 (请求结果并“拉”参数。)
因此,我的最后一个样本专用于显示简化的数据驱动的执行(原则上):
#include <iostream>
#include <vector>
#include <functional>
struct ConstInt {
int n;
std::vector<std::function<void(int)>> out;
ConstInt(int n): n(n) { eval(); }
void link(std::function<void(int)> in)
{
out.push_back(in); eval();
}
void eval()
{
for (std::function<void(int)> &f : out) f(n);
}
};
struct MultiplyIntInt {
int n1, n2; bool received1, received2;
std::vector<std::function<void(int)>> out;
void set1(int n) { n1 = n; received1 = true; eval(); }
void set2(int n) { n2 = n; received2 = true; eval(); }
void link(std::function<void(int)> in)
{
out.push_back(in); eval();
}
void eval()
{
if (received1 && received2) {
int prod = n1 * n2;
for (std::function<void(int)> &f : out) f(prod);
}
}
};
struct Print {
const char *text;
explicit Print(const char *text): text(text) { }
void set(int n)
{
std::cout << text << n << '\n';
}
};
int main()
{
// setup data flow
Print print("Result: ");
MultiplyIntInt mul;
ConstInt const3(3), const4(4);
// link nodes
const3.link([&mul](int n) { mul.set1(n); });
const4.link([&mul](int n) { mul.set2(n); });
mul.link([&print](int n) { print.set(n); });
// done
return 0;
}
牢记数据流图图像(由koman900 – OP提供),out
向量表示节点的输出,其中方法set()
/ set1()
/ {{1} }代表输入。
输出:
set2()
连接图后,源节点(Result: 12
和const3
)可能会将新结果推入其输出,这可能会或可能不会导致以下操作重新计算。
对于图形表示,操作员类别应另外提供某种类型的基础结构(例如,检索名称/类型以及可用的输入和输出,并且可能是用于通知状态更改的信号)。
当然,可以将两种方法(数据驱动和需求驱动的执行)结合起来。 (中间的节点可以更改其状态,并请求新的输入以随后推送新的输出。)
答案 1 :(得分:2)
您可以使用templates。
template <typename UnderlyingClass>
class MultiplyBy2
{
typedef int (UnderlyingClass::*func)();
func input_func;
UnderlyingClass *obj;
public:
MultiplyBy2(UnderlyingClass *object, func i) : obj(object), input_func(i) {}
int output()
{
return (obj->*input_func)() * 2;
}
};
int main()
{
// test
InputInt ii;
MultiplyBy2<InputInt> mii{ &ii, &InputInt::output };
RandomInt ri;
MultiplyBy2<RandomInt> mri{ &ri, &RandomInt::rand10 };
}
答案 2 :(得分:0)
这有点令人费解。但是我认为您应该制作一个返回值的接口或类,并且对象应该从中继承。然后,运算符类可以采用从基类/接口继承的任何类。例如,使一个可以存储int并具有输出方法的BaseInt类/ RandomInt和InputInt应该继承自BaseInt