使用指向成员函数的指针来确定调用哪个函数

Use pointer to member function to determine which function to call

我有以下 class:

class Karen
{
    public:
        Karen(void);
        ~Karen(void);

        void complain(std::string level);

    private:
        void    debug(void)     const;
        void    info(void)      const;
        void    warning(void)   const;
        void    error(void)     const;
};

complain 函数接收一个字符串,其中可以包含 debug、info、warning 或 error,它必须调用适当的函数而不使用 if/elseif/else 的森林,而是使用指针到成员函数。 complain的原型是给我的。我是成员函数指针的新手,我不确定如何管理它。我的尝试之一是:

void    Karen::complain(std::string level)
{

    std::string     *p = &level;
    void            (Karen::*f)(void)       const;

    (this->*(*p))();
}

最后一行的语法不正确,但我正在尝试(this->*(content of pointer p))()但我不知道如何写这个。有人可以帮助我吗?

编辑我只允许使用C++98

通过成员函数指针调用成员函数的语法是

(this->*memf)();

你不能神奇地将字符串变成成员函数指针。草率地说,函数名称在运行时不存在。如果你想要这样的映射,你需要自己提供。没有办法解决这个问题。您可以避免的是使用 std::unordered_map:

的“if-else 森林”
#include <unordered_map>
#include <string>
#include <iostream>

class Karen
{
    public:
        void complain(std::string level) {
            static const std::unordered_map<std::string, void(Karen::*)() const>  m{
                {"debug",&Karen::debug},
                {"info",&Karen::info},
                {"warning",&Karen::warning},
                {"error",&Karen::error}
            };
            auto it = m.find(level);
            if (it == m.end()) return;
            (this->*(it->second))();
        }

    private:
        void debug(void) const { std::cout << "debug\n"; }
        void info(void) const { std::cout << "info\n"; }
        void warning(void) const { std::cout << "warning\n"; }
        void error(void) const { std::cout << "error\n"; }
};

int main() {
    Karen k;
    k.complain("info");
}

Live Demo

如评论中所述,您可以使用枚举代替字符串。如果可能,您应该使用编译器的帮助,它可以诊断枚举中的错字,但不能诊断字符串中的错字。或者,您可以直接将成员函数指针传递给 complain。那么 complain 的实现将是微不足道的,不需要分支。虽然这需要方法是 public 并且调用者必须处理成员函数指针。


如果你不被允许使用 C++11 或更新版本,你应该和你的老师认真谈谈。很快 C++20 将成为事实上的标准,并且情况发生了很大变化。我不再精通 C++98,所以这里只是对上面的内容进行快速修复以使其以某种方式工作。您不能使用 std::unordered_map 但有 std::map 并且地图的初始化相当麻烦:

#include <map>
#include <string>
#include <iostream>

class Karen
{
    typedef void(Karen::*memf_t)() const; 
    typedef std::map<std::string,void(Karen::*)() const> map_t;

    public:
        void complain(std::string level) {
            map_t::const_iterator it = get_map().find(level);
            if (it == get_map().end()) return;
            (this->*(it->second))();
        }

    private:
        const map_t& get_map(){
            static const map_t m = construct_map();
            return m;
        }
        const map_t construct_map() {
            map_t m;
            m["debug"] = &Karen::debug;
            m["info"] = &Karen::info;
            m["warning"] = &Karen::warning;
            m["error"] = &Karen::error;
            return m;
        }
        void debug(void) const { std::cout << "debug\n"; }
        void info(void) const { std::cout << "info\n"; }
        void warning(void) const { std::cout << "warning\n"; }
        void error(void) const { std::cout << "error\n"; }
};

int main() {
    Karen k;
    k.complain("info");
}

Live Demo

让我们从所需的包含开始。

#include <cassert>
#include <iostream>
#include <map>

以字符串形式提供日志级别可能会导致错误,因为编译器无法检查拼写错误。因此,enum::class 是确定日志级别的更好选择。

enum class LogLevel {
  DEBUG,
  INFO,
  WARNING,
  ERROR
};

C++ 不提供获取给定字符串的函数指针的方法。在编译和链接函数名称后,所有函数名称都已替换为它们在内存中的适当地址。因此,我们首先需要以一种允许我们根据需要查找它们的方式存储函数指针。为此,您可以使用静态 class 属性,并将函数指针存储在 std::map.

class Logger
{
    public:
        Logger();
        ~Logger();

        void complain(LogLevel level);

    private:
        void debug() const;
        void info() const;
        void warning() const;
        void error() const;

        using HandlerMap = std::map<LogLevel, void (Logger::*)(void) const>;
        static HandlerMap handlers;
};

Logger::HandlerMap Logger::handlers{
  {LogLevel::DEBUG, &Logger::debug},
  {LogLevel::INFO, &Logger::info},
  {LogLevel::WARNING, &Logger::warning},
  {LogLevel::ERROR, &Logger::error}
};

complain方法只需要查找正确的函数指针并调用该方法即可。

void Logger::complain(LogLevel level) {
  assert(handlers.find(level) != handlers.end());
  (this->*handlers[level])();
}

其余函数如下所示。

Logger::Logger() {}
Logger::~Logger() {}

void Logger::debug() const { std::cout << "debug" << std::endl; }
void Logger::info() const { std::cout << "info" << std::endl; }
void Logger::warning() const { std::cout << "warning" << std::endl; }
void Logger::error() const { std::cout << "error" << std::endl; }

int main(int argc, char* argv[]) {
  Logger k;
  k.complain(LogLevel::DEBUG);
  k.complain(LogLevel::INFO);
  k.complain(LogLevel::WARNING);
  k.complain(LogLevel::ERROR);
}

注意,如果您坚持使用字符串,可以将LogLevel替换为std::string,将LogLevel::<member>替换为相应的字符串。


同样可以使用 C++98 实现。但是,您将需要更多的引导。

#include <cassert>
#include <iostream>
#include <map>

enum LogLevel {
  LL_DEBUG,
  LL_INFO,
  LL_WARNING,
  LL_ERROR
};

class Logger
{
    public:
        Logger();
        ~Logger();

        void complain(LogLevel level);

        typedef std::map<LogLevel, void (Logger::*)() const> HandlerMap;

        friend struct LoggerInit;
    private:
        void debug() const;
        void info() const;
        void warning() const;
        void error() const;

        static HandlerMap handlers;
};

Logger::HandlerMap Logger::handlers = Logger::HandlerMap();

struct LoggerInit {
  LoggerInit() {
    Logger::handlers[LL_DEBUG] = &Logger::debug;
    Logger::handlers[LL_INFO] = &Logger::info;
    Logger::handlers[LL_WARNING] = &Logger::warning;
    Logger::handlers[LL_ERROR] = &Logger::error;
  }
} logger_init;

void Logger::complain(LogLevel level) {
  assert(handlers.find(level) != handlers.end());
  (this->*handlers[level])();
}

Logger::Logger() {}
Logger::~Logger() {}

void Logger::debug() const { std::cout << "debug" << std::endl; }
void Logger::info() const { std::cout << "info" << std::endl; }
void Logger::warning() const { std::cout << "warning" << std::endl; }
void Logger::error() const { std::cout << "error" << std::endl; }

int main(int argc, char* argv[]) {
  Logger k;
  k.complain(LL_DEBUG);
  k.complain(LL_INFO);
  k.complain(LL_WARNING);
  k.complain(LL_ERROR);
}