天天看点

Modern c++ visitor 模式的实现

最近在代码里常常看到访问者模式的代码,自己也实现一下,看看是否对访问者模式有着正确的理解。

有两个元素,不同的的访问者访问的时候,会出现不同的效果,我们可以考虑使用访问者模式来实现
这个例子里,这两个元素是两种编程语言,python和go(PythonLang和GoLang)
访问者也有两种,真正的程序员和虚假的程序员(RealCoder 和 FakeCoder)
真正的程序员学一门语言会认真的编写代码来熟练技巧,虚假的程序员会搭建一下环境就ok了,这就是不同的访问者访问同一个元素会有不同的效果           

Talk and Code both are not cheap

首先我们在visitor.h定义一个visitor的接口类,这里引用文件memory是为了使用std里的智能指针std::shared_ptr

#ifndef VISTOR_PATTERN_VISITOR_H
#define VISTOR_PATTERN_VISITOR_H
// 访问者接口类,Visit 接受需要访问的元素,进行不同的操作
#include<memory>
class GoLang;
using SharedGoLang = std::shared_ptr<GoLang>;
class PythonLang;
using SharedPython = std::shared_ptr<PythonLang>;
class Ivisitor
{
public:
    virtual ~Ivisitor() = default;
    virtual void Visit(SharedGoLang goLang) = 0;
    virtual void Visit(SharedPython goLang) = 0;
};
#endif //VISTOR_PATTERN_VISITOR_H
           

之后我们定义一个被访问者的接口类LangElememt.h,由于被访问者都是需要学习的编程语言,所以这个类就叫ILangElement

吧。

#ifndef VISTOR_PATTERN_LANGELEMEMT_H
#define VISTOR_PATTERN_LANGELEMEMT_H
#include"visitor.h"
// 被访问者接口类,Accept接口接受一个具体的访问者
class ILangElement
{
public:
    virtual ~ILangElement() = default;
    virtual void Accept(std::shared_ptr<Ivisitor> vistor) = 0;
};
#endif //VISTOR_PATTERN_LANGELEMEMT_H
           

访问者和被访问者接口定义好了,这两个类中比较重要的就是Ivisitor::Visit()和ILangElement::Accept(),我们的使用思路是ILangElement::Accept()作为外部调用的函数,它接受一个visitor,而在Visit()内部,把this指针作为参数,即把自身传递给Ivisitor::Visit()。

接下来是实现各个子类,(访问者,被访问者)

被访问者是两种编程语言,那访问者就只能是程序员了,不过这里有两种程序员,一种是真正的程序员,他们会认真的学习编写,强化自己,另外一种是虚假的程序员,他们装了pycharm或者goland这些IDE就浅尝辄止了。他们都定义在了RealCoder.h里了(这里偷懒了)

#ifndef VISTOR_PATTERN_REALCODER_H
#define VISTOR_PATTERN_REALCODER_H
#include"visitor.h"
#include "Languages.h"
#include<iostream>
// 定义了两个访问者
class RealCoder : public Ivisitor
{
public:
    void Visit(SharedGoLang goLang)
    {
        std::cout<<"Real coder will write code to learn golang......"<<std::endl;
    }
    void Visit(SharedPython pythonLand)
    {
        std::cout<<"Real coder will write code to learn python......"<<std::endl;
    }
};
class FakeCoder : public Ivisitor
{
public:
    void Visit(SharedGoLang goLang)
    {
        std::cout<<"Fake coder will install some  GoLang IDE tools......"<<std::endl;
    }
    void Visit(SharedPython goLang)
    {
        std::cout<<"Fake coder will install some  pyhton IDE tools......"<<std::endl;
    }
};
#endif //VISTOR_PATTERN_REALCODER_H
           

被访问者定义在了Languages.h,主要作用是Accept()接受vistor并调用visit. visit函数需要传入被访问者的this指针,因此需要把this指针转化成shared_ptr,这些类还需要继承std::enable_shared_from_this

#ifndef VISTOR_PATTERN_LANGUAGES_H
#define VISTOR_PATTERN_LANGUAGES_H
#include"LangElememt.h"
/*定义了两个被访问者GoLang PythonLang。
*/
class GoLang : public ILangElement,public std::enable_shared_from_this<GoLang>
{
public:
     void Accept(std::shared_ptr<Ivisitor> vistor) override
     {
         vistor->Visit(shared_from_this()); // visit()接受智能指针,这里需要把this指针转化为shared_ptr
     }
};
class PythonLang : public ILangElement,public std::enable_shared_from_this<PythonLang>
{
public:
    void Accept(std::shared_ptr<Ivisitor> vistor) override
    {
        vistor->Visit(shared_from_this());
    }
};
#endif //VISTOR_PATTERN_LANGUAGES_H
           

接下来就是具体的使用了。在main.cpp里调用以下函数

#include <iostream>
#include "RealCoder.h"
#include"Languages.h"
#include "LangHome.h"
void TestvisitorDirectly()
{
    std::shared_ptr<GoLang> pgolang{new GoLang};
    std::shared_ptr<Ivisitor> pcoder(new RealCoder());
    std::shared_ptr<Ivisitor> pfakecoder(new FakeCoder());
    // 这里只能使用智能指针的方式调用调用Accept(),否则会出现bad weak_ptr
    pgolang->Accept(pcoder);
    pgolang->Accept(pfakecoder);
    std::shared_ptr<PythonLang> ppythonlang{new PythonLang};
    ppythonlang->Accept(pcoder);
    ppythonlang->Accept(pfakecoder);
}
int main() {
    TestvisitorDirectly();
    return 0;
}
           

这里我们创建被访问者的时候又使用了智能指针,例如

std::shared_ptr<GoLang> pgolang{new GoLang};           

可不可以直接创建一个对象,然后通过对象+“.”的方式调用呢

GoLang pgolang;
    std::shared_ptr<Ivisitor> pcoder(new RealCoder());
    pgolang.Accept(pcoder);           

Answer is NO!!!!错误是

terminate called after throwing an instance of 'std::bad_weak_ptr'

  what():  bad_weak_ptr

具体的原因感兴趣的同学可以自己查阅c++ std智能指针的相关文献。这里如果把被访问者作为普通指针会出现同样的错误。

GoLang * pgolang = new GoLang();
    std::shared_ptr<Ivisitor> pcoder(new RealCoder());
    pgolang->Accept(pcoder);           

这里讲的有点啰嗦,最后,我们把这一系列的调用封装在一个新类里,把这个类作为对外的接口(这里纯粹是为了美观,与设计模式无关),LangHome.h

#ifndef VISTOR_PATTERN_LANGHOME_H
#define VISTOR_PATTERN_LANGHOME_H
#include "RealCoder.h"
#include"Languages.h"
#include <list>
#include<algorithm>
using SharedLangs = std::shared_ptr<ILangElement>;
class LangHome
{
public:
    void Attach(SharedLangs lang)
    {
        langs_.push_back(lang);
    }

    void  Detach(SharedLangs lang)
    {
        langs_.remove(lang);
    }

    void Accept(std::shared_ptr<Ivisitor> visitor)
    {
        /*
         * 下面三种方式都完成了对list的遍历操作
         * 即list里面的每一个被访问者都会接受访问者
         * /
        /*for (auto it = langs_.begin(); it != langs_.end(); ++it)
        {
            (*it)->Accept(visitor);
        }*/
      /* for(const auto & lang:langs_)
        {
            lang->Accept(visitor);
        }*/
       std::for_each(langs_.begin(),langs_.end(),[visitor]( std::shared_ptr<ILangElement> p){p->Accept(visitor);});

    }
private:
    std::list<SharedLangs> langs_;

};
#endif //VISTOR_PATTERN_LANGHOME_H
           

这个类中std::list<SharedLangs> langs_ 通过Attach函数把所有的被访问者加入链表,有访问者来的时候调用LangHome::Accept(),让list的所有元素都调用自身的Accept()

LangHome::Accept() 实现了三种遍历的方式,经典for循环,for_each中的lambda表达式以及类似与java中的一种for循环。只是为了提供一下思路。

这个类的用法如下

void TestVistotbyUseLangHome()
{
    // 把需要的调用封装在了类 LangHome;
    LangHome langHome;
    std::shared_ptr<GoLang> pgolang{new GoLang};
    std::shared_ptr<PythonLang> ppythonlang{new PythonLang};
    langHome.Attach(pgolang);
    langHome.Attach(ppythonlang);
    std::shared_ptr<Ivisitor> pcoder(new RealCoder());
    std::shared_ptr<Ivisitor> pfakecoder(new FakeCoder());
    langHome.Accept(pcoder);
    langHome.Accept(pfakecoder);
}           

就像boost库一样,modern c++也实现了自己的访问者模式,借助std::variant 和std::visit 以及彷函数operator(),我么可以用更小的代码量来实现这个功能.

std::variant 是c++17引入的类似于union的一个类型,我们用std::variant<SharedGoLang, SharedPython>类型定义的变量来存储某一个编程语言对应的智能指针

using var_langs_t = std::variant<SharedGoLang, SharedPython>;
    std::shared_ptr<GoLang> pgolang{new GoLang};
    std::shared_ptr<PythonLang> ppythonlang{new PythonLang};
    var_langs_t v1 {pgolang}, v2 {ppythonlang};           

相应的访问者类只需要根据不同的语言类型添加不同的operator(),例如:

class RealCoder_C
{
public:
    void operator()(SharedGoLang goLang)
    {
        std::cout<<"Real coder will write code to learn golang......"<<std::endl;
    }
    void operator()(SharedPython pythonLand)
    {
        std::cout<<"Real coder will write code to learn python......"<<std::endl;
    }
};           

RealCoder_C需要实现两个operator(),对应SharedGoLang 和SharedPython,完整的调用方式如下

#ifndef VISTOR_PATTERN_VAR_VISITOR_H
#define VISTOR_PATTERN_VAR_VISITOR_H


#include<variant>
#include<iostream>
#include<vector>
#include<algorithm>
#include"visitor.h"
#include "Languages.h"

// 关于std::variant 调用的简单例子
using var = std::variant<int, double, std::string>;
struct Visitor
{
    void operator()(double it)
    {
        std::cout<< "this is a double:"<<it <<std::endl;
    }
    void operator()(std::string it)
    {
        std::cout<< "this is a string:"<<it <<std::endl;
    }
    void operator()(int it)
    {
        std::cout<< "this is a int:"<<it <<std::endl;
    }
};

void visit_element()
{
    std::vector<var> var_vec;
    var var1 {1}, var2{3.4}, var3{"hello"};
    var_vec.push_back(var1);
    var_vec.push_back(var2);
    var_vec.push_back(var3);
    Visitor visitor;
    for(auto &&elem:var_vec)
    {
        std::visit(visitor,elem);
    }

}

// 用c++17 原生的访问者模式完成之前realcoder and fakecoder的功能

class RealCoder_C
{
public:
    void operator()(SharedGoLang goLang)
    {
        std::cout<<"Real coder will write code to learn golang......"<<std::endl;
    }
    void operator()(SharedPython pythonLand)
    {
        std::cout<<"Real coder will write code to learn python......"<<std::endl;
    }
};
class FakeCoder_C
{
public:
    void operator()(SharedGoLang goLang)
    {
        std::cout<<"Fake coder will install some  GoLang IDE tools......"<<std::endl;
    }
    void operator()(SharedPython goLang)
    {
        std::cout<<"Fake coder will install some  pyhton IDE tools......"<<std::endl;
    }
};

void test_coder_c()
{
    using var_langs_t = std::variant<SharedGoLang, SharedPython>;
    std::shared_ptr<GoLang> pgolang{new GoLang};
    std::shared_ptr<PythonLang> ppythonlang{new PythonLang};
    var_langs_t v1 {pgolang}, v2 {ppythonlang};
    std::vector<var_langs_t> var_vec;
    var_vec.push_back(v1);
    var_vec.push_back(v2);
    RealCoder_C rc;
    FakeCoder_C fc;

    for(auto &&elem:var_vec)
    {
        std::visit(rc,elem);
        std::visit(fc,elem);
    }

}

#endif //VISTOR_PATTERN_VAR_VISITOR_H
           

调用时直接在main函数里调用test_code_c() 即可。

上述例子的源码可在这里获取(https://github.com/BenSherry/Design.git)

下一篇: 高性能服务

继续阅读