C++ 使用数组中的 class 执行函数

C++ execute function with class from array

我需要能够通过在数组中查找函数指针来调用作为 class 成员的函数。这个 class 将有子 classes 做同样的事情,但如果他们不能为该功能提供资金,则调用父 class。为了简单起见,我删掉了大部分代码。剩下的如下图。

终极考验是创造:

1) Mammal : public Animal
1.1) Cat : public Mammal
1.2) Dog : public Mammal
2) Reptile : public Animal
2.1) Bird : public Reptile

我想尽可能干净地构建它,这样 Bob Martin 叔叔就会对我微笑。现在,我想他只会刺激我一下,所以如果能帮助我重构它以使其变得更好,我们将不胜感激。

class Animal {
public:
#define CMD_EAT             1
#define CMD_SLEEP           2
#define CMD_MAKENOISE       3

private:
    const int _actions;
    const char* _name;

public:
    //  Define a pointer to a function within this class that takes
    //  an INT as its argument
    typedef void(Animal::*animalFunc)(int);

private:
    //  Define an array of pointers to action functions
    animalFunc _actionPointers[];  //<<< COMPILE ERROR: "incomplete type is not allowed"

    //  Define an array of action names
    char* _animalActions[];

public:
    Animal(int actions, char* name) : _actions(actions), _name(name) {
        _actionPointers[_actions] = NULL;
        _animalActions[_actions] = NULL;
        registerCommands();
    }

    //  Define an array of pointers to action functions
    //animalFunc animalCommands[MAX_ANIMAL_CMD];

    //  Register all commands supported by this class
    virtual void registerCommands() {
        registerCommand(CMD_EAT, "EAT", &Animal::eat);
        registerCommand(CMD_SLEEP, "SLEEP", &Animal::sleep);
        registerCommand(CMD_MAKENOISE, "MAKE NOISE", &Animal::makeNoise);
    }

    void registerCommand(int code, char* action, void (Animal::*animalFunc)(int)) {
        _animalActions[code - 1] = action;
        _actionPointers[code - 1] = animalFunc;
    }

    void exec(int code, int value) {
        Serial.print("Executing  ");
        Serial.print(code);
        *(this->_actionPointers[code])(value);  //<<< THIS DOESN'T COMPILE
    }

    const char* getName() {
        return _name;
    }

    //  base class methods
    virtual void eat(int times) {}

    virtual void sleep(int times) {}

    void makeNoise(int times) {}
};

void main() {
    //  Step 1: Create pointer to an instance of an animal object
    Animal *pAnimal = new Animal(3, "ANIMAL");
    pAnimal->exec(CMD_EAT, 1);
    pAnimal->exec(CMD_SLEEP, 1);
}

我遇到了两个无法解决的编译错误。它们在代码中突出显示。

我重构了 Animal class 以移除大量噪音。还使用了 C++11 功能。如果你没有 c++11,那么它可以很容易地被 boost 取代。我基本上改变了您进行注册的方式。此外,我还删除了很多在当前上下文中对我来说没有意义的内容,但对您来说可能很重要。

#include <iostream>
#include <map>
#include <memory>
#include <functional>

class Animal {
public:
  enum Action {
    CMD_EAT = 1,
    CMD_SLEEP,
    CMD_MAKENOISE
  };

private:
    const std::string _name;
    std::map<Action, std::function<void(int)>> _actionsMap;

public:
    Animal(const std::string& name) : _name(name) {
        registerCommands();
    }

    //  Register all commands supported by this class
    virtual void registerCommands() {
        using namespace std::placeholders;
        registerCommand(CMD_EAT,       std::bind(&Animal::eat, this, _1));
        registerCommand(CMD_SLEEP,     std::bind(&Animal::sleep, this, _1));
        registerCommand(CMD_MAKENOISE, std::bind(&Animal::makeNoise, this, _1));
    }

    void registerCommand(Action code, std::function<void(int)> cb) {
      _actionsMap.emplace(code, cb);
    }

    void exec(Action action, int value) {
        //Serial.print("Executing  ");
        //Serial.print(code);
        //TODO: Check if present in map
        _actionsMap[action](value);
    }

    //  base class methods
    virtual void eat(int times) { std::cout << "Eat\n"; }

    virtual void sleep(int times) { std::cout << "Sleep\n"; }

    void makeNoise(int times) {}
};

int main() {
    //  Step 1: Create pointer to an instance of an animal object
    std::unique_ptr<Animal> pAnimal(new Animal("Animal"));
    pAnimal->exec(Animal::CMD_EAT, 1);
    pAnimal->exec(Animal::CMD_SLEEP, 1);

    return 0;
}

可以做的第一件事是用枚举替换 #defines,并添加总命令数。

#define CMD_EAT             1
#define CMD_SLEEP           2
#define CMD_MAKENOISE       3

变成

enum {
     CMD_EAT
     , CMD_SLEEP
     , CMD_MAKENOISE
     , COMMAND_COUNT
};

接下来,我们应该确保代码是常量正确的。由于我们使用的是字符串常量,所有字符串变量和函数参数都应该是 char const* 而不是 char*.

在此之后,我们可以将函数指针和名称组合在一个结构中,因为它们属于一起。请注意,由于我们有成员函数指针的 typedef,我们可以使用它。

struct command_info
{
    animalFunc handler;
    char const* name;
};

由于我们现在知道编译时的命令数,并且我们有上面的结构,我们可以有一个固定大小的数组:

command_info _commands[COMMAND_COUNT];

我们也可以从构造函数中删除 actions 参数。

由于我们有一个固定大小的数组,因此在访问数组之前验证索引很重要:

if (code < COMMAND_COUNT) { //...

接下来,你有虚方法,所以你的class也应该有一个虚析构函数:

virtual ~Animal() {}

我们接近尾声 -- 接下来是您 invoke the member function pointer 的问题。正确的方法(考虑到上述修改)是:

(this->*_commands[code].handler)(value);

最后,您在程序结束时泄漏了内存。

delete pAnimal;

不过,资源管理还是用RAII比较好。由于您使用的是 AVR 并且没有可用的标准 C++ 库,因此您可以定义一个简单的句柄 class,类似于

struct animal_ptr {
    animal_ptr(Animal* a) : ptr(a) {}
    ~animal_ptr() { delete a; }
    Animal* ptr;
}

完成修改后的代码

注意:我注释掉了涉及 Serial 的行,这样我就可以在没有它的情况下进行编译。

class Animal 
{
public:
    enum {
        CMD_EAT
        , CMD_SLEEP
        , CMD_MAKENOISE
        , COMMAND_COUNT
    };

    //  Define a pointer to a function within this class that takes
    //  an INT as its argument
    typedef void(Animal::*animalFunc)(int);
    struct command_info
    {
        animalFunc handler;
        char const* name;
    };

public:
    Animal(char const* name)
        : _name(name)
    {
        registerCommands();
    }

    //  Register all commands supported by this class
    virtual void registerCommands() {
        registerCommand(CMD_EAT, "EAT", &Animal::eat);
        registerCommand(CMD_SLEEP, "SLEEP", &Animal::sleep);
        registerCommand(CMD_MAKENOISE, "MAKE NOISE", &Animal::makeNoise);
    }

    void registerCommand(int code, char const* action, animalFunc fn) {
        if (code < COMMAND_COUNT) {
            _commands[code].name = action;
            _commands[code].handler = fn;
        }
    }

    void exec(int code, int value) {
        if (code < COMMAND_COUNT) {
            //Serial.print("Executing  ");
            //Serial.print(code);
            (this->*_commands[code].handler)(value);
        }
    }

    char const* getName() {
        return _name;
    }

    //  base class methods
    virtual void eat(int times) {}

    virtual void sleep(int times) {}

    void makeNoise(int times) {}
    
    
private:
    char const* _name;
    
    //  Define an array of pointers to action functions
    command_info _commands[COMMAND_COUNT];
};

int main() {
    //  Step 1: Create pointer to an instance of an animal object
    Animal *pAnimal = new Animal("ANIMAL");
    pAnimal->exec(Animal::CMD_EAT, 1);
    pAnimal->exec(Animal::CMD_SLEEP, 1);
    delete pAnimal;
}

Sample on Coliru