动态创建函数并获取指针

Posted

技术标签:

【中文标题】动态创建函数并获取指针【英文标题】:Dynamically create a function and get a pointer 【发布时间】:2017-05-18 15:08:45 【问题描述】:

我正在使用 Arduino 和电机编码器来跟踪电机的旋转。为此,我在 Arduino 上使用中断。我可以创建一个函数,一个 ISR,只要引脚上的信号发生变化,处理器就会执行该函数。中断/ISR 组合的工作方式如下:

void setup() 
    attachInterrupt(1,ISR_function,FALLING);


void ISR_function() 
    // do something

鉴于我有多个带编码器的电机,我决定开设一个课程来处理这个问题。但是,attachInterrupt 方法需要一个函数指针,而且我知道在 C++ 中不能有一个指向对象实例的方法函数的指针。所以这样的事情是行不通的:

class Encoder 
    public:
        Encoder(void);
        void ISR_function(void);
    private:
        // Various private members


Encoder::Encoder() 
    attachInterrupt(1,ISR_function,FALLING);


Encoder::ISR_function() 
    // Do some interrupt things with private members

因为ISR_function 不是静态的。然而,ISR_function 执行的代码依赖于每个特定实例的私有数据成员。

是否可以动态创建函数?然后检索指向该函数的指针?几乎就像在 javascript 中一样:

class Encoder 
    public:
        Encoder(void);
        void* ISR_function(void);
    private:
        // Various private members


Encoder::Encoder() 
    attachInterrupt(1,ISR_function(),FALLING);


Encoder::ISR_function() 
    return dynamicFunctionPointer;

这可能吗?如果没有,如何在不手动创建单独的静态ISR_functions 的情况下完成我想做的事情。

【问题讨论】:

“在 C++ 中你不能有一个指向方法函数的指针”...什么?是的,你可以。 可以吗?在实例对象上? 你的术语很奇怪:“方法函数”/“实例对象”。这些东西都不存在。但是,如果我们更正您的术语,是的,您当然可以。指向成员的指针绝对是一回事。但是,您不能将其视为普通的指向函数的指针,这使得它们对于 C 回调毫无用处。 @CoryKramer:阅读该问题的答案。不,您不能将“类方法”作为函数指针传递。 @HurricaneDevelopment 如果你把所有这些东西都静态化,那么你可以将它们作为常规函数访问。 (虽然你只能有一组变量) 【参考方案1】:
// type of an interrupt service routine pointer
using ISR = void(*)();

// a fake version of the environment we are working with
// for testing purposes
namespace fake_environment 
    enum bobFALLING;

    ISR isrs[100] = 0;

    void attachInterrupt(int i, void(*f)(), bob) 
        isrs[i] = f;
    

    void runInterrupt(int i) 
        isrs[i]();
    


// type storing a pointer to member function
// as a compile-time constant
template<class T, void(T::*m)()>
struct pmf ;

// stores a pointer to a class instance
// and a member function.  Invokes it
// when called with operator().  Type erases
// stuff down to void pointers.
struct funcoid 
  using pfunc = void(*)(void*);
  pfunc pf = 0;
  void* pv = 0;
  void operator()()const  pf(pv); 
  template<class T, void(T::*m)()>
  funcoid(T* t, pmf<T,m>):
    pv(t)
  
    // create a lambda, then decay it into a function pointer
    // this stateless lambda takes a void* which it casts to a T*
    // then invokes the member function m on it.
    pf = +[](void* pt) 
      (static_cast<T*>(pt)->*m)();
    ;
  
  funcoid()=default;
;

// a global array of interrupts, which have a this pointer
// and a member function pointer type erased:
namespace client 
  enum interrupt_count = 20;
  std::array<funcoid, interrupt_count> interrupt_table = ;
  // with a bit of work, could replace this with a std::vector        


// some metaprogramming utility code
// this lets me iterate over a set of size_t at compile time
// without writing extra helper functions at point of use.
namespace utility 
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) 
    return [](auto&& f)->decltype(auto) 
      return f(std::integral_constant<std::size_t, Is>...);
    ;
  
  template<std::size_t N>
  auto index_upto( std::integral_constant<std::size_t, N> = ) 
    return index_over( std::make_index_sequence<N> );
  


// builds an array of interrupt service routines
// that invoke the same-index interrupt_table above.
namespace client 
  // in g++, you'd write a helper function taking an `index_sequence`
  // and take the code out of that lambda and build the array there:
  std::array<ISR, interrupt_count> make_isrs() 
    // creates an array of ISRs that invoke the corresponding element in interrupt_table.
    // have to do it at compile time, because we are generating 20 different functions
    // each one "knows" its index, then storing pointers to them.
    // Could be done with a lot of copy-pasta or a macro
    return ::utility::index_upto< interrupt_count >()(
      [](auto...Is)->std::array<ISR, interrupt_count>
        return  [] interrupt_table[decltype(Is)::value](); ... ;
      
    );
  
  // isr is a table of `void(*)()`, suitable for use
  // by your interrupt API.  Each function pointer "knows" its
  // index, which it uses to invoke the appropraite `interrupt_table`
  // above.
  auto isr = make_isrs();
  // with a bit of work, could replace this with a std::vector        


// interrupt is the interrupt number
// index is the index in our private table (0 to 19 inclusive)
// t is the object we want to use
// mf is the member function we call
// kind is FALLING or RISING or the like
// index must be unique, that is your job.
template<class T, void(T::*m)()>
void add_interrupt( int interrupt, int index, T* t, pmf<T, m> mf, fake_environment::bob kind ) 
  client::interrupt_table[index] = t, mf;
  fake_environment::attachInterrupt(interrupt,client::isr[index],kind);



class Encoder 
  public:
    Encoder():Encoder(1, 7) ;
    Encoder(int interrupt, int index);
    void ISR_function(void);
    // my choice for some state:
    std::string my_name;
;

Encoder::Encoder(int interrupt, int index) 
  add_interrupt( interrupt, index, this, pmf<Encoder, &Encoder::ISR_function>, fake_environment::FALLING );


void Encoder::ISR_function() 
  // display state:
  std::cout << my_name << "\n";


int main() 
  Encoder e0;
  e0.my_name = "Hello World";
  fake_environment::runInterrupt(1);
  Encoder e1(0, 10);
  e1.my_name = "Goodbye World";
  fake_environment::runInterrupt(0);

不在 g++ 中编译并使用 C++14。

确实解决了您的问题。 g++ 问题在make_isrs,可以用冗长的复制粘贴初始化来代替。 C++14 来自 index_uptoindex_over,同样可以为 C++11 重新设计。

Live example.

但是,ISR 应该是最小的;我怀疑您应该只记录消息并在其他地方处理它,而不是与对象状态交互。

【讨论】:

我非常喜欢阅读您的回答。不过要理解它是一件很困难的事情:) @PasserBy 是的。添加了 cmets。可能会有所帮助。 读完这本书真的很棒,很难,但很棒。在为指向成员函数template&lt;class T, void(T::*m)()&gt; struct pmf ; 的指针创建编译时常量时,我​​不理解模板中的第二个参数。你能解释一下吗? @HurricaneDevelopment 第一个是涉及的类的类型,第二个是编译时成员函数指针。我们需要我们的函数是stateless,这意味着任何状态都必须在编译时知道。通过将编译时指针传递给成员函数,可以调用函数指针而不将其传递给 lambda 或捕获它。我本可以像void*void(*)(void*) 那样将PMF 存储在“类型已擦除存储”中,但这在便携方面很棘手;我发现这更容易。 @HurricaneDevelopment 还添加了更多 cmets,包括获取 g++ 编译它的模糊方法。【参考方案2】:

要调用成员函数,您需要一个实例来调用它,因此用于中断似乎不是一个好的选择。

来自pointers-to-members:

如果没有对象来调用它,成员函数就毫无意义。

非静态成员函数有一个隐藏参数对应 this 指针。 this 指针指向对象的实例数据。系统中的中断硬件/固件无法提供此指针参数。您必须使用“普通”函数(非类成员)或静态成员函数作为中断服务例程。

一种可能的解决方案是使用静态成员作为中断服务例程,并让该函数在某处查找应在中断时调用的实例/成员对。因此效果是在中断时调用成员函数,但出于技术原因,您需要先调用中间函数。

【讨论】:

你应该在这个答案中包含链接所说的任何内容。【参考方案3】:

首先,您可以提取指向类方法的指针并调用它:

auto my_method_ptr = &MyClass::my_method;
....
(myClassInstance->*my_method_ptr)(); // calling via class ptr
(myclassInstance.*my_method_ptr)(); // calling via class ref

这基本上将myClassInstance 指针作为隐式参数传递给MyClass::my_method,可通过this 访问。

不幸的是,AVR 中断控制器不能调用类方法,因为硬件只对简单的指针进行操作,并且不能使用隐式参数调用该方法。为此,您需要一个包装函数。

MotorEncoderClass g_motor; // g_ for global

void my_isr() 
    g_motor.do_something();


int main() 
    // init g_motor with relevant data
    // install my_isr handler
    // enable interrupts
    // ... do rest of stuff
    return 0;

    将您的类实例创建为全局变量。 创建调用该方法的普通函数 使用相关数据初始化您的电机类 安装 my_isr 作为 IRQ 处理程序。 按开始开始:)

【讨论】:

感谢您的详尽解释。看到我将制作MotorEncoderClass 的多个实例,您对处理多个包装类my_isr() 有什么建议吗? 如果您有很多硬件中断,那么只需创建多个包装器 isr 函数并调用不同的 MotorEncoderClass 实例。如果您有一个中断,请使用if-elseswitch 中的中断源。

以上是关于动态创建函数并获取指针的主要内容,如果未能解决你的问题,请参考以下文章

PHP动态获取类构造函数并调用它来创建一个新实例[重复]

如何从函数返回动态分配的指针数组?

将数组动态分配到带有指针参数的函数中

尝试使用动态内存分配创建节点指针数组

创建动态单链表

动态内存与智能指针