问题
I need to be able to call a function that is a member of a class by looking up the function pointer in an array. This class will have sub classes that do the same thing but call the parent class if they cannot fund the function. To make matters simple, I have cut out most of the code. What remains is shown below.
The ultimate test is to create:
1) Mammal : public Animal
1.1) Cat : public Mammal
1.2) Dog : public Mammal
2) Reptile : public Animal
2.1) Bird : public Reptile
I want to build this as cleanly as possible so Uncle Bob Martin would smile down on me. Right now, I think he would just give me a kick in the pants, so any help refactoring this to make it better would be deeply appreciated.
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);
}
I am getting two compile errors that I haven't been able to get around. They are highlighted in the code.
回答1:
First thing that can be done is to replace #define
s with an enum, and add a total command count.
#define CMD_EAT 1
#define CMD_SLEEP 2
#define CMD_MAKENOISE 3
becomes
enum {
CMD_EAT
, CMD_SLEEP
, CMD_MAKENOISE
, COMMAND_COUNT
};
Next, we should make sure the code is const-correct. Since we're using string constants, all the string variables and function arguments should be char const*
rather than char*
.
After this, we could combine the function pointer and the name in one structure, since they belong together. Notice that since we have a typedef for the member function pointer, we can use that.
struct command_info
{
animalFunc handler;
char const* name;
};
Since we now know the number of commands at compile time, and we have the above structure, we can have a single fixed-size array:
command_info _commands[COMMAND_COUNT];
We can also drop the actions
parameter from the constructor.
Since we have a fixed size array, it's important to validate the index before accessing the array:
if (code < COMMAND_COUNT) { //...
Next, you have virtual methods, so your class should also have a virtual destructor:
virtual ~Animal() {}
We're getting close to the end -- next there's a problem with how you invoke the member function pointer. The correct way (with the above mentioned modifications in mind) is:
(this->*_commands[code].handler)(value);
Finally, you leak memory at the end of your program.
delete pAnimal;
However, it would be better to use RAII for resource management. Since you're on AVR and don't have the Standard C++ Library available, you could just define a simple handle class, something along the lines of
struct animal_ptr {
animal_ptr(Animal* a) : ptr(a) {}
~animal_ptr() { delete a; }
Animal* ptr;
}
Complete revised code
NB: I commented out the lines involving Serial
, so that I can compile without it.
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
回答2:
I have refactored the Animal class to remove lots of noise. Also have used C++11 features. If you dont have c++11, then it can be easily replaced by boost. I have basically changed the way you are doing the registrations. Also I have removed quite a few stuff which did not make sense to me in the current context, but may be important for you.
#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;
}
来源:https://stackoverflow.com/questions/37490956/c-execute-function-with-class-from-array