• C++经验(十)-- 保存成员函数的函数地址,避免switch-case的大量使用


    不知道你们有没有遇到过这种情况的,一个函数里面有40多个if-else或者switch-case的,并且还用了两边,我遇到了,大致数了下,这两个包含了40多个if-elseswitch-case的函数总共占用了400 多行

    看到这种代码真的是会让人头大。试着研究了下,发现他的每个if或者每个case里面调用的函数格式是一样的,全都是 void XXXX();或者bool XXX();类型。这不就巧了吗,这跟那啥很像啊。然后就开始试着优化。

    其实看到这种代码的时候,脑子里第一浮现的应该就是策略模式了。毕竟这完全就是一个简化版的策略嘛,只不过调用的函数都是在一个类里面而已。

    但不久,我就放弃用策略了,这么多的 case,每个策略都新建一个类,那岂不是要40多个,这对于偷懒惯了的人可不是一次友好的体验。接着就去重新琢磨其他的方法。

    很快,突然想起来观察者这种东西,好像这几年写Qt写的多了,用惯了信号和槽,都忘记了那些比较基础的东西了。观察者模式不就是被观察者保存了观察者某个函数的地址,在合适的地方进行了调用吗?

    那这种方式是不是可行呢?

    在类构造的时候将成员函数的地址通过某种手段保存下来,然后在需要的时候,精准地找到需要调用的成员函数的地址进行调用。

    开始了尝试。

    首先就是需要选定一个容器,这个容器要满足能够存储函数的地址,并且方便查找。该说不说,满足二分查找的 map 就可以了。

    选定了容器之后,就需要定义容器的key-value的类型了。当然 key的类型好选择,不管是string还是枚举都可以,只要这个是唯一的就行。但是value的类型呢?都知道是需要保存成员函数的地址,但是这个地址该怎么提取出

    一个通用的类型呢?

    可是,回调不就是这样子的吗?

    这些成员函数有着一样的格式,把他们都看作是一个回调函数不就行了吗?

    于是就有了下面:

    include<iostream>
    #include<map>
    #include<string>
    
    class Test
    {
    public:
        Test()
        {
            auto map_insert = [&](std::string key, pFunc ptr)
            {
                m_mFuncPtr.emplace(std::make_pair(key, ptr));
            };
    
            map_insert("print1", &Test::print1);
            map_insert("print2", &Test::print2);
            map_insert("print3", &Test::print3);
        }
    
        ~Test() = default;
    
        void print(const std::string& func)
        {
            auto itor = m_mFuncPtr.find(func);
    	    if (itor == m_mFuncPtr.end() || nullptr == itor->second)
    		{
                return;
            }
    
    	    (this->*itor->second)();    //调用函数
        }
    
    private:
        void print1()
        {
            std::cout << "this is print1 func..." << std::endl;
        }
    
        void print2()
        {
            std::cout << "this is print2 func..." << std::endl;
        }
    
        void print3()
        {
            std::cout << "this is print3 func..." << std::endl;
        }
    
    private:
    
        typedef void (Test::* pFunc)();
    
        std::map<std::string, pFunc> m_mFuncPtr;
    };
    
    int main(int argc, char* argv[])
    {
        Test test;
        test.print("print1");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    当然,函数的声明是可以放在类外面的,放在类外面的话就是这样的。

    class Test;
    typedef void (Test::*pFunc)();
    struct CmdPtr
    {
    	pFunc ptr = nullptr;
    	CmdPtr()
    	{
    		reset();
    	}
    	void reset()
    	{
    		ptr = nullptr;	
    	}	
    };
    
    class Test
    {
    public:
        Test()
        {
            auto map_insert = [&](std::string key, pFunc ptr)
            {
                CmdPtr cfg;
                cfg.ptr = ptr;
                m_mFuncPtr.emplace(std::make_pair(key, cfg));
            };
    
            map_insert("print1", &Test::print1);
            map_insert("print2", &Test::print2);
            map_insert("print3", &Test::print3);
        }
    
        void print(const std::string& func)
        {
            auto itor = m_mFuncPtr.find(func);
    	    if (itor == m_mFuncPtr.end() || nullptr == itor->second.ptr)
    		{
                return;
            }
    
    	    (this->*itor->second.ptr)();    //调用函数
        }
    
    private:
        std::map<std::string, CmdPtr> m_mFuncPtr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    其实这样的修改,跟上面的那种直接写在类里面的是没什么区别的。但好处是满足了我们以对象管理资源的建议。

    上述代码中,用了 emplace 而不是 insert 来进行map元素的插入,主要原因是因为这种方式效率更高,可以参考《stl标准库系列之–map》的第八节,里面有对 map 容器的几种插入数据的方式进行比较详细的说明和比较。

    最后说一句,我们天天在重复造轮子,为什么不能在造轮子的过程中学会偷懒了,学会偷懒,才会简化你的代码。

  • 相关阅读:
    iOS 16 中 CoreData 托管对象发生变化但其衍生 (Derived) 属性在 SwiftUI 中不刷新的解决
    如何配置Log4j以便将日志信息输出到文件,并指定日志文件的格式?请解释Log4j中的PatternLayout,并给出一个常用的日志格式模式。
    阿里云centos7.9乱码问题
    认识并安装WSL
    JS 流行框架(八):ScrollReveal
    将cookie字符串转成editthiscookie插件的json格式
    《MongoDB入门教程》第18篇 文档更新之$unset操作符
    SSRF服务器端请求伪造
    科技云报道:拉响警报!2023年三大网络安全威胁不容忽视
    分布式微服务 - 3.降级熔断 - 3.Sentinel
  • 原文地址:https://blog.csdn.net/tax10240809163com/article/details/125458864