多态的演变

马克思曾经说过:能用一个接口处理的事情,就别写俩。

多态是为了让用户端能用同一个接口,处理不同类型的对象而设置的。当然,这里的用户端是广义的,你写的一套接口,给另一个同事调用,那个同事就是你的用户端,哪怕是你自己在另一个文件里调用,这“另一个文件”也可以称为是用户端。

之所以要这么做,原因很多,但总结起来就一句话:让用户用起来爽。

口说无凭,先上个小代码看看:

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 class Test1{};
  6 class Test2{};
  7 class Test3{};
  8 class Test4{};
  9 
 10 void common_interface(const Test1 &t) {
 11   cout << "deal with Test1..." << endl;
 12 }
 13 void common_interface(const Test2 &t) {
 14   cout << "deal with Test2..." << endl;
 15 }
 16 void common_interface(const Test3 &t) {
 17   cout << "deal with Test3..." << endl;
 18 }
 19 
 20 
 21 void foo() {
 22   Test1 t1;
 23   Test2 t2;
 24   Test3 t3; 
 27   common_interface(t1);
 28   common_interface(t2);
 29   common_interface(t3);
 31 }
 32 int main() {
 33   foo();
 34   return 0;
 35 }

这里使用函数重载,来实现了所谓的多态,无论是哪种Test类型,用户可以无脑的传递给common_interface。

熟悉C++的都知道,函数重载只是一种比较low的多态,叫静态多态。相对应的,还有一种动态多态,也就是通过继承体系,虚函数,虚表指针等实现的多态,才是真正意义上的多态。

这种多态实现起来就相对麻烦了,必须要有一个继承体系,然后通过虚函数,通过基类的指针或引用巴拉巴拉……

这种小demo,只要学过C++的,脑袋里肯定已经有画面了,就不贴代码了,何况马克思说过,简单的代码没必要贴出来占地方!

所以我们来看一个稍微复杂的场景:假如我们要处理的数据是从上游发送过来的(这种发送也是广义的,并不一定是通过了网络)。然后我们要用同一个接口处理这些数据,如果发送端和接收端是同步的,那用上面例子的函数重载就可以解决这种问题。

但如果二者是异步的,问题就会复杂很多,首先必须要有一个异步队列,而这个队列里要能放入各种类型的数据,这就要求这些数据有一个共同的基类,然后将基类指针作为队列存储对象。但上游传过来的消息并不一定存在这种共基类的关系,最广泛的场景,他们可能是风马牛不相及的东西。

为了将风马牛都放入一个队列中,就必须再构造一个继承体现,作为他们的“管理者”,这些类不含业务功能,是真正意义上的管理者。

异步队列是一个相对独立的模块,可以在网上找到很多,为了方便,这里提供一个:

//safe_queue.h

#include <condition_variable>
#include <deque>
#include <mutex>

template <typename T>
class SafeQueue {
 public:
  using lock_type = std::unique_lock<std::mutex>;

 public:
  SafeQueue() = default;

  ~SafeQueue() = default;

  template<typename IT>
  void push(IT &&item) {
    static_assert(std::is_same_v<T, std::decay_t<IT>>, "Item type is not convertible!!!");
    {
      lock_type lock{mutex_};
      queue_.emplace_back(std::forward<IT>(item));
    }
    cv_.notify_one();
  }

  auto pop() -> T {
    lock_type lock{mutex_};
    cv_.wait(lock, [&]() { return !queue_.empty(); });
    auto front = std::move(queue_.front());
    queue_.pop_front();
   return front;
  }

 private:
  std::deque<T> queue_;
  std::mutex mutex_;
  std::condition_variable cv_;
};

然后就是业务代码了 

#include <iostream>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include "safe_queue.h"


using namespace std;

class Test1 {
public:
  Test1() = default;
  Test1(const Test1 &other) {
    cout << "Test1 Copy Constructor..." << endl;
  }
  Test1(const Test1 &&other) {
    cout << "Test1 Move Constructor..." << endl;
  }
};

class Test2 {
public:
  Test2() = default;
  Test2(const Test2 &other) {
    cout << "Test2 Copy Constructor..." << endl;
  }
  Test2(const Test2 &&other) {
     cout << "Test2 Move Constructor..." << endl;
  }
};

class Test3 {
public:
  Test3() = default;
  Test3(const Test3 &other) {
    cout << "Test3 Copy Constructor..." << endl;
  }
  Test3(const Test3 &&other) {
    cout << "Test3 Move Constructor..." << endl;
  }
};

class Test4 {
public:
  Test4() = default;
  Test4(const Test4 &other) {
    cout << "Test4 Copy Constructor..." << endl;
  }
  Test4(const Test4 &&other) {
    cout << "Test4 Move Constructor..." << endl;
  }
};
lass DealBase{
public:
  virtual void common_interface() {}
};


class DealTest1 : public DealBase {
public:
  template<typename T>
  DealTest1(T &&t): t_{std::forward<T>(t)} { }
  virtual void common_interface() {
    cout << "deal with Test1..." << endl;
  }
private:
  Test1 t_;
};

class DealTest2 : public DealBase {
public:
  template<typename T>
  DealTest2(T &&t): t_{std::forward<T>(t)} { }
  virtual void common_interface() {
    cout << "deal with Test2..." << endl;
  }
private:
  Test2 t_;
};

class DealTest3 : public DealBase {
public:
  template<typename T>
  DealTest3(T &&t): t_{std::forward<T>(t)} { }
  virtual void common_interface() {
    cout << "deal with Test3..." << endl;
  }
private:
  Test3 t_;
};

class DealTest4 : public DealBase {
public:
  template<typename T>
  DealTest4(T &&t): t_{std::forward<T>(t)} { }
private:
  Test4 t_;
};


class Widget {
  public:
    Widget() :
      worker_{std::thread([this](){ this->worker_func();})}, 
      to_quit_{false} { }
    ~Widget() {
      to_quit_ = true;
      if (worker_.joinable()) {
        worker_.join();
        cout << "thread is destroy" << endl;
      }
    }
  void add_task(DealBase* pdb) {
    queue.push(pdb);
  }
  private:
    void worker_func() {
      while(!to_quit_.load()) {
        //do something with class member
        std::this_thread::sleep_for(std::chrono::seconds(1));
        cout << "thread is working" << endl;
        while(true) {
          DealBase *p = queue.pop();
          p->common_interface();
          sum_mutable_data += 1;
        }
      }
    }
  private://线程对象
    mutable std::atomic_bool to_quit_; 
    SafeQueue<DealBase*> queue;
    int sum_mutable_data;
};


void foo() {
  Test1 t1;
  Test2 t2;
  Test3 t3;
  Test4 t4;
  DealBase *p1 = new DealTest1{t1};
  DealBase *p2 = new DealTest2(std::move(t2));
  DealBase *p3 = new DealTest3(std::move(t3));
  DealBase *p4 = new DealTest4(std::move(t4));

  Widget w;
  w.add_task(p1);
  w.add_task(p2);
  w.add_task(p3);
  w.add_task(p4);
  w.add_task(123);
  std::this_thread::sleep_for(std::chrono::seconds(10));
  delete p1;
  delete p2;
  delete p3;
  delete p4;
}

int main() {

  foo();
  return 0;
}

正如代码所示,为了用户端(新建的线程)用起来爽(不管队列里是什么类型对象,直接就是p->common_interface();),需要很多额外的代码来实现。

这种为了实现某种功能,需要写很多无用代码代码的行为,可以认为是编程语言的缺点,即表达某种语义的语法过于复杂。而这种语义,就是面向对象三大特性之一的多态。

所以后面C++ 17对这部分语法进行了优化,引入了variant 和 visit函数,使用新的语法完成同样的语义,就简单多了:

#include <iostream>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <variant>
#include "safe_queue.h"


using namespace std;

class Test1 {
public:
  Test1() = default;
  Test1(const Test1 &other) {
    cout << "Test1 Copy Constructor..." << endl;
  }
  Test1(const Test1 &&other) {
    cout << "Test1 Move Constructor..." << endl;
  }
};

class Test2 {
public:
  Test2() = default;
  Test2(const Test2 &other) {
    cout << "Test2 Copy Constructor..." << endl;
  }
  Test2(const Test2 &&other) {
    cout << "Test2 Move Constructor..." << endl;
  }
};

lass Test3 {
public:
  Test3() = default;
  Test3(const Test3 &other) {
    cout << "Test3 Copy Constructor..." << endl;
  }
  Test3(const Test3 &&other) {
    cout << "Test3 Move Constructor..." << endl;
  }
};

class Test4 {
public:
  Test4() = default;
  Test4(const Test4 &other) {
    cout << "Test4 Copy Constructor..." << endl;
  }
  Test4(const Test4 &&other) {
    cout << "Test4 Move Constructor..." << endl;
  }
};

template<class... Ts> struct overloaded : Ts... {using Ts::operator()...; };

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;


template <typename... Ts>
class Widget {
  public:
    using queue_t = SafeQueue<std::variant<Ts...>>;
  public:
   Widget() :
      worker_{std::thread([this](){ this->worker_func();})}, //可以在构造函数里起线程,也可以在其他成员函数里
      to_quit_{false} { }
    ~Widget() {
      to_quit_ = true;
      if (worker_.joinable()) {
        worker_.join();
        cout << "thread is destroy" << endl;
      }
    }
  template<typename IT>
  void add_task(IT &&item) {
    queue.push(std::forward<IT>(item));
  }
  private:
  void worker_func() {
    //do something with class member
    while(!to_quit_.load()) {
      cout << "thread is working" << endl;
      const auto &item = queue.pop();
      std::visit(overloaded{
          [](auto &msg){ cout << "deal unknown type..." << endl;},
          [](const Test1 &t){ cout << "deal Test1 type..." << endl;},
          [](const Test2 &t){ cout << "deal Test2 type..." << endl;},
          [](const Test3 &t){ cout << "deal Test3 type..." << endl;},
          [](const Test4 &t){ cout << "deal Test4 type..." << endl;}
          },item);

     sum_mutable_data += 1;
      std::this_thread::sleep_for(std::chrono::seconds(1));

    }
  }
  private:
    std::thread worker_; //线程对象
    mutable std::atomic_bool to_quit_; //线程的回收标志
    queue_t queue;
    int sum_mutable_data;
};


void foo() {
  Test1 t1;
  Test2 t2;
  Test3 t3;
  Test4 t4;

  Widget<Test1, Test2, Test3, Test4> w;
  w.add_task(std::move(t1));
  w.add_task(std::move(t2));
  w.add_task(std::move(t3));
  w.add_task(std::move(t4));
  std::this_thread::sleep_for(std::chrono::seconds(10));
}

int main() {

  foo();
  return 0;
}
                                             

 代码中还有一些要注意的细节值得探讨,但本文只关注为了实现多态,语言层面的支持。

总结:传统的多态实现需要构建继承体系,类型约束比较强,使用起来比较复杂;C++17中新的多态非常简练,且几乎没有类型约束,但灵活的代价是风险需要程序员自己承担。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值