将任何类型的对象的函数传递给C ++中的另一个对象

时间:2018-08-08 10:36:23

标签: c++

我正在创建一个节点系统(类似于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;
}

3 个答案:

答案 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

Live Demo on coliru


正如我在与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

Live Demo on coliru

通过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} }代表输入。

Snapshot of a dataflow graph in Blender

输出:

set2()

Live Demo on coliru

连接图后,源节点(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