作为处理程序传递给另一个成员函数的多个单个对象的成员函数

时间:2018-10-16 20:01:38

标签: c++ oop arduino arduino-uno arduino-ide

我的线程似乎与previous one类似,我设法使用lambda函数对其进行了处理。 但是我又遇到了一个问题,我猜想lambda函数不是解决问题的方法,我实际上还没有完全理解。

我需要什么: 在我的arduino项目中,我经常需要在运行时手动更改变量的值(例如,占空比,led的状态或其他)。 因此,我实现了一个以易于使用的方式提供该功能的类,这意味着我可以通过解析以我自己的协议格式设置的输入字符串(通过串行,tcp,telnet等方式)来访问全局变量。 另一个要求是,还应该可以将事件链接到这样的变量,该变量在通过我的界面访问该变量时执行。

我做了什么: 在简化版本中,我的“类参数”具有变量地址的属性,选择访问它的名称以及指向要绑定的函数的指针。 相应的读取和写入方法句柄用于访问给定地址的实际值。 原始版本还具有包含有关访问级别和数据类型的信息的属性。 另外,单独的事件也必定会读写事件,但是我将忽略这一部分,以及所有相关函数都将重载以访问不同数据类型的变量这一事实。 因此,我会坚持使用简化版本,因为它足够简短以证明我的问题。

要使我的类正常工作,我需要一个函数'new_parameter(,)'将我的参数存储在向量中,而函数'read_param()'/'write_param()'实际上要通过字符串访问特定参数。 这些最后的函数代表我的解析器,但我也将其省略。

下面的代码似乎运行良好,即使我确定有更好的方法也可以。 对于MCVE,它是使用g ++ 7.3.0编译的,但最终必须使用avr-g ++进行编译。

//parameter.h
using namespace std;

class Parameter {
  public:
    Parameter(int *p, std::string n, void (*e)());
    long int address;
    std::string name;
    void (*event)();

    int read();
    void write(int v);
};
Parameter::Parameter (int *p, std::string n, void (*e)()) {
  address = (long int) p;
  name=n;
  event=e;
}

int Parameter::read () {
  if(event!=NULL){
    event();
  }
  int value = *reinterpret_cast<int*>(address);
  return value;
}

void Parameter::write (int v) {
  if(event!=NULL){
    event();
  }
  *(int*)(address)=v;
}

std::vector<Parameter> parameters;

void new_parameter (int *p, std::string n="defaultname", void (*e)()=NULL) {
  parameters.push_back(Parameter(p,n,e));
}

int read_param(std::string n) {
  for (int i=0;i<parameters.size();i++) {
    if (parameters[i].name == n) {
      int v = parameters[i].read();
      return v;
    }
  }
  return -1;
}

void write_param(std::string n, int v) {
  for (int i=0;i<parameters.size();i++) {
    if (parameters[i].name == n) {
      parameters[i].write(v);
      break;
    }
  }
}


//simple_main.cpp
#include <vector>
#include <string>
#include <locale>
#include <functional>
#include "device.h"

//a global variable
int variable1=-1;
//an event executed when global variable is accessed
void variable1_event () {printf("variable1 event\n");}

int main () {
  //create parameter object for variable
    new_parameter(&variable1,"variable1",variable1_event);

  //read parameter
  printf("1: %i\n",variable1);
  printf("2: %i\n",read_param("variable1"));

  //change value
  write_param("variable1",10);

  //read parameter
  printf("3: %i\n",variable1);
  printf("4: %i\n",read_param("variable1"));
}

执行时main()具有以下输出:

1: -1
variable1 event
2: -1
variable1 event
3: 10
variable1 event
4: 10

这符合我过去的要求。到目前为止,一切都很好!

在我当前的项目中,我有数量可变的从设备通过I2C连接到我的单片机(ESP32),每个从设备都有一组相同的参数(例如,用于温度控制的设定温度),现在我想通过以前的参数访问展示了“类参数”。 由于从设备是同一种设备,因此创建“类设备”是显而易见的解决方案。 然后,根据连接的i2c从站数量,创建任意数量的对象。 使用“类设备”的意思是,我的参数现在将指向一个属性,而相应的事件函数现在是一种事件方法。 问题在于,此事件方法必须将数据传输到特定的从属设备,因此不能是静态的,因为必须使用不同的i2c地址来调用它(对吗?)。 我已经尽我所能尝试了一切,但是还没有使它起作用。

这是我对“设备类”的简化版本:

//parameter.h
#define MAX_DEVICES 4

int device_count=0;

class Device {
    public:
    Device();
        Device(uint8_t i2c_address);

        bool is_default;
        uint8_t i2c_address;
        int data;

        void i2c_write();
};
Device::Device () {
  is_default=true;
}
Device::Device (uint8_t i2c) {
    is_default=false;
    i2c_address=i2c;
}

Device devices [MAX_DEVICES];

void Device::i2c_write () {
    printf("call to i2c_write (address %i, data %i)\n",i2c_address,data);
}

int get_free_index () {
    for (int i=0; i<MAX_DEVICES; i++) {
        if (devices[i].is_default) return i;
    }
    return -1;
}

void new_device (uint8_t i2c) {
    int new_index=get_free_index();
    if (new_index>=0) {
    devices[new_index]=Device(i2c);
//    new_parameter(&devices[new_index].data, "device"+std::to_string(new_index)+"data",  devices[new_index].i2c_transmit)
  }
    else printf("Error: exceeded maximum number of engines\n");
}

请参阅下面的高级主要功能,以了解如何处理设备。

//advanced_main.cpp
#include <vector>
#include <string>
#include <locale>
#include <functional>
#include "parameter2.h"
#include "device2.h"

int variable1=-1;
void variable1_event () {printf("variable1 event\n");}

int main () {
  //create parameter object for variable
    new_parameter(&variable1,"variable1",variable1_event);
  new_device(10);
  new_device(10);

  //read/write parameter
  printf("1: %i\n",read_param("variable1"));
  printf("2: %i\n",read_param("device0data"));
  printf("3: %i\n",read_param("device1data"));
  write_param("variable1",10);
  write_param("device0data",20);
  write_param("device1data",30);
  printf("4: %i\n",read_param("variable1"));
  printf("5: %i\n",read_param("device0data"));
  printf("6: %i\n",read_param("device1data"));
}

如果可以使用,我期望的输出是:

variable1 event
1: -1
call to i2c_transmit (address 19, data 123)
2: 123
call to i2c_transmit (address 23, data 123)
3: 123
variable1 event
call to i2c_transmit (address 19, data 123)
call to i2c_transmit (address 23, data 123)
variable1 event
4: 10
call to i2c_transmit (address 19, data 20)
5: 20
call to i2c_transmit (address 23, data 30)
6: 30

但实际上它甚至在此版本中都无法编译:

device.h:40:120: error: invalid use of non-static member function ‘void Device::i2c_transmit()’
devices[new_index].data, "device"+std::to_string(new_index)+"data",  devices[new_index].i2c_transmit)

我尝试将成员函数'i2c_transmit()'传递给'class parameter'的构造函数的所有其他方式也不起作用,即使我经常理解为什么,我也不知道它如何工作。

创建本地对象,将该对象的副本存储到全局数组并仅在该副本上工作是不重要的吗?我猜,那就是我上面的代码所做的。我还尝试将'Device devices [MAX_DEVICES];'声明为静态,但是没有用。我尝试使用Lambda函数,但也没有运气... 很难说出我还尝试了什么,但是我仍然认为我的总体结构还是有问题。 我愿意接受新的建议,但是由于“类参数”是库的一部分,所以我希望不要更改该类!

2 个答案:

答案 0 :(得分:1)

void (*event)()是一个设计不良的回调。 C风格代码中的回调既具有函数指针,又具有void* ,如下所示:

void (*event)(void*);
void* state;

答案 1 :(得分:0)

您可以在回叫中添加参数,如其他答案所述。另一种选择是完全消除回调并使用OOP设计。创建一个围绕int的包装器类,并使用用于获取和设置的虚拟函数。

class Parameter {
  public:
    Parameter(String n, int default_value = 0) :
        name(n), value(default_value) {}

    // Virtual functions that can be overridden but have default functionality
    virtual int read() {
        return value;
    }
    virtual int write(int new_value) {
        return value = new_value;
    }

    String name;
  protected:
    int value;

    // Below are static functions and variables
    // ----
    // std lib is not supported on Arduino, so use an array 
    // Arduino programs are small enough that you should be
    // able to make an educated guess at MAX_PARAMETERS
    // Also note it is an array of pointers for virtual functions to work
    static Parameter *all_values[MAX_PARAMETERS];
    static int parameter_count;
  public:
    static bool add_param(Parameter *p) {
        if (parameter_count < MAX_PARAMETERS) {
            all_values[parameter_count++] = p;
            return true;
        }
        return false;
    }
    static Parameter * find_param(String name) {
         for (int i = 0; i < parameter_count; i++) {
             if (all_values[i]->name == name) return all_values[i];
         }
         return nullptr;
    }
};
Parameter * Parameter::all_values[MAX_PARAMETERS];
int Parameter::parameter_count = 0;

对于i2c参数,您可以扩展此类

class Device : public Parameter
{
  protected:
     uint8_t address;
  public:
    Device(std::string n, uint8_t i2c_address) :
        address(i2c_address), Parameter(n) {}

    // Override these
    int read() {
        // Add your code here to read the value from the bus....
        return value;
    }
    int write(int new_value) {
        Parameter::write(new_value);
        // Add your code here to write the value to the bus....
        return value;
    }
};

要使用它,您可以执行以下操作:

// Create and add regular param
Parameter::add_param(new Parameter("test1"));
// Create and add i2c device param
Parameter::add_param(new Device("test2", 99));

Parameter::find_param("test1")->write(100);
int x = Parameter::find_param("test1")->read();

Parameter::find_param("test2")->write(123);
int y = Parameter::find_param("test2")->read();

// You can also use temp vars to simplify
Parameter *some_device = Parameter::find_param("test2");
some_device->write(100);
int z = some_device->read();

我知道Arduinos的资源非常有限,所以我很想看看这段代码如何在设备上执行。