前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出C++类型擦除

深入浅出C++类型擦除

作者头像
高性能架构探索
发布2024-05-17 14:14:04
1230
发布2024-05-17 14:14:04
举报
文章被收录于专栏:技术随笔心得技术随笔心得

你好,我是雨乐~

今天,我们聊聊C++编程中的一个常用方法类型擦除

写在前面

类型擦除是 C++ 中一种用于实现多态性的编程技术,它允许在不牺牲性能或引入不必要的运行时开销的情况下进行多态性操作。通过隐藏对象的实际类型并提供统一的接口,类型擦除使得可以以多态的方式处理不同类型的对象,同时在运行时推迟对实际类型的确定。

今天,通过示例,逐步讲解什么是类型擦除,以及如何用类型擦除技术来实现多态机制~

从一个示例开始

想必我们在一开始学习多态的时候,通过在类中定义virtual函数,然后通过指针或者引用来进行函数调用,以达到不同的类型执行的函数调用结构不同,在本节,仍然以此举例。

接口类

示例代码如下:

代码语言:javascript
复制
class Shape {
 public:
  virtual double GetArea() const = 0;
  virtual ~Shape() {}
};

这是一个接口类,类中就定义了两个函数,一个为GetArea获取面积,另一个声明为virtual的析构函数,旨在防止内存泄漏。

派生类

代码如下:

代码语言:javascript
复制
class Square : public Shape {
 public:
  Square(double side) : side_(side) {}

  double GetArea() const {
    return side_ * side_;
  }
 private:
  double side_;
};

class Rectangle : public Shape {
 public:
  Rectangle(double width, double length) : w_(width), h_(length) {}

  double GetArea() const {
    return w_ * h_;
  }
 private:
  double w_;
  double h_;
};

class Circle : public Shape {
    
 public:
  Circle(double radius) : radius_(radius) {}
  
  double GetArea() const {
    return 3.14 * radius_ * radius_;
  }
 private:
  double radius_;
};

在上述代码中,定义了3个派生类,分别为Square、Rectangle以及Circle。

现在,一个我们熟悉的使用场景出现了,代码如下:

代码语言:javascript
复制
double GetArea(Shape *shape) {
  return shape->GetArea();
}

int main() {
  Square s{1.0};
  Rectangle r{1.0, 2.0};
  Circle c{3.0};
  std::vector<Shape*> shape{&s, &r, &c};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

emm,输出结果显而易见:

代码语言:javascript
复制
1
2
28.26

好了,问题来了,如果上面的3个类没有一个公共的Base类,就是说上述3个类分别是完全独立的类,那么vector如何编写?

代码语言:javascript
复制
std::vector<???> shape{&s, &r, &c};

下面,开始针对这个问题进行分析解决~

方案一

既然既没有共同基类,又想存储在容器中,这种只能有一种方法强制构造基类,当然了也有人可能会说采用其他方式,比如std::optional void*等,这种就不在考虑范围内了,毕竟本文的主题是类型擦除嘛~

共同基类如下:

代码语言:javascript
复制
class MyShape {
 public:
  virtual double GetArea() const = 0;
  virtual ~MyShape() {}
};

同样的,对于Square、Rectangle以及Circle也可以采用类似的方式,只是在实现上较之前有所改动:

代码语言:javascript
复制
class MySquare : public MyShape {
 public:
  MySquare(double side) : square_(std::make_unique<Square>(side)){}

  double GetArea() const {
    return square_->GetArea();
  }
 private:
  std::unique_ptr<Square> square_;
};

class MyRectangle : public MyShape {
 public:
  MyRectangle(double width, double length) : rectangle_(std::make_unique<Rectangle>(width, length)) {}

  double GetArea() const {
    return rectangle_->GetArea();
  }
 private:
  std::unique_ptr<Rectangle> rectangle_;
};

class MyCircle : public MyShape {
    
 public:
  MyCircle(double radius) : circle_(std::make_unique<Circle>(radius)) {}
  
  double GetArea() const {
    return circle_->GetArea();
  }
 private:
  std::unique_ptr<Circle> circle_;
};

使用方式则修改为如下这种:

代码语言:javascript
复制
int main() {
  MySquare s{1.0};
  MyRectangle r{1.0, 2.0};
  MyCircle c{3.0};
  std::vector<MyShape*> shape{&s, &r, &c};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

就不再赘述了~

完整代码如下:

代码语言:javascript
复制
#include <iostream>
#include <chrono>
#include <vector>
#include <memory>

class MyShape {
 public:
  virtual double GetArea() const = 0;
  virtual ~MyShape() {}
};

class Square {
 public:
  Square(double side) : side_(side) {}

  double GetArea() const {
    return side_ * side_;
  }
 private:
  double side_;
};

class Rectangle {
 public:
  Rectangle(double width, double length) : w_(width), h_(length) {}

  double GetArea() const {
    return w_ * h_;
  }
 private:
  double w_;
  double h_;
};

class Circle  {
    
 public:
  Circle(double radius) : radius_(radius) {}
  
  double GetArea() const {
    return 3.14 * radius_ * radius_;
  }
 private:
  double radius_;
};

class MySquare : public MyShape {
 public:
  MySquare(double side) : square_(std::make_unique<Square>(side)){}

  double GetArea() const {
    return square_->GetArea();
  }
 private:
  std::unique_ptr<Square> square_;
};

class MyRectangle : public MyShape {
 public:
  MyRectangle(double width, double length) : rectangle_(std::make_unique<Rectangle>(width, length)) {}

  double GetArea() const {
    return rectangle_->GetArea();
  }
 private:
  std::unique_ptr<Rectangle> rectangle_;
};

class MyCircle : public MyShape {
    
 public:
  MyCircle(double radius) : circle_(std::make_unique<Circle>(radius)) {}
  
  double GetArea() const {
    return circle_->GetArea();
  }
 private:
  std::unique_ptr<Circle> circle_;
};

double GetArea(MyShape *shape) {
  return shape->GetArea();
}

int main() {
  MySquare s{1.0};
  MyRectangle r{1.0, 2.0};
  MyCircle c{3.0};

  std::vector<MyShape*> shape{&s, &r, &c};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

方案二

让我们回到上一节,现在假设一下,如果此时有几十个甚至上百个这样的类,那么每一个类都进行如此封装,emm,工作量难以想象,在此,可以采用模板方式来解决此类问题,如下:

代码语言:javascript
复制
template<typename T>
class Wrapper : public MyShape {
 public:
  Wrapper(T *t) : t_(t) {}
  double GetArea() {
    return t_->GetArea();
  }
 private:
  T *t_ = nullptr;
};

使用方式则如下:

代码语言:javascript
复制
double GetArea(Shape *shape) {
  return shape->GetArea();
}

int main() {
  Square s{1.0};
  Rectangle r{1.0, 2.0};
  Circle c{3.0};

  
  std::vector<MyShape*> shape{new Wrapper(&s), 
  new Wrapper(&r),
  new Wrapper(&c)};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

完整的代码形如:

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <memory>

class MyShape {
 public:
  virtual double GetArea() const = 0;
  virtual ~MyShape() {}
};

class Square {
 public:
  Square(double side) : side_(side) {}

  double GetArea() const {
    return side_ * side_;
  }
 private:
  double side_;
};

class Rectangle {
 public:
  Rectangle(double width, double length) : w_(width), h_(length) {}

  double GetArea() const {
    return w_ * h_;
  }
 private:
  double w_;
  double h_;
};

class Circle  {
    
 public:
  Circle(double radius) : radius_(radius) {}
  
  double GetArea() const {
    return 3.14 * radius_ * radius_;
  }
 private:
  double radius_;
};

template<typename T>
class Wrapper : public MyShape {
 public:
  Wrapper(T *t) : t_(t) {}
  double GetArea() const {
    return t_->GetArea();
  }
 private:
  T *t_ = nullptr;
};


double GetArea(MyShape *shape) {
  return shape->GetArea();
}

int main() {
  Square s{1.0};
  Rectangle r{1.0, 2.0};
  Circle c{3.0};

  
  std::vector<MyShape*> shape{new Wrapper(&s), 
  new Wrapper(&r),
  new Wrapper(&c)};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

终极方案

在上一节内容中,其实类型擦除基本思想已经体现出来了,在本节对其进行部分修改,如下:

代码语言:javascript
复制
class Area {
 public:
  template <typename T> 
  void Add(T* shape)  { 
    shape_.emplace_back(new Wrapper(shape)); 
  }

  void Print() {
    for (auto &&elem : shape_) {
      elem->GetArea();
    }
  }
  ~Area() {
    for (auto &&elem : shape_) {
      delete elem;
    }
  }
 private:
  class MyShape {
   public:
    virtual double GetArea() const = 0;
    virtual ~MyShape() {}
  };

  template<typename T>
  class Wrapper : public MyShape {
   public:
    Wrapper(T *t) : t_(t) {}
    double GetArea() const {
      return t_->GetArea();
    }
   private:
    T *t_ = nullptr;
  };
  std::vector<MyShape*> shape_;
};

其本意是生成一个大类Area,然后将前面的Myshape、Wrapper等类放进去,然后新增两个成员函数Add()和Print()分别用以添加对象和输出。

完整的示例代码如下:

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <memory>

class Area {
 public:
  template <typename T> 
    void  Add(T* shape)  { 
        shape_.emplace_back(new Wrapper(shape)); 
    }

    void Print() {
      for (auto &&elem : shape_) {
        std::cout << elem->GetArea() << "\n";
      }
    }
 private:
  class MyShape {
 public:
  virtual double GetArea() const = 0;
  virtual ~MyShape() {}
};

template<typename T>
class Wrapper : public MyShape {
 public:
  Wrapper(T *t) : t_(t) {}
  double GetArea() const {
    return t_->GetArea();
  }
 private:
  T *t_ = nullptr;
};

std::vector<MyShape*> shape_;
};

class MyShape {
 public:
  virtual double GetArea() const = 0;
  virtual ~MyShape() {}
};

class Square {
 public:
  Square(double side) : side_(side) {}

  double GetArea() const {
    return side_ * side_;
  }
 private:
  double side_;
};

class Rectangle {
 public:
  Rectangle(double width, double length) : w_(width), h_(length) {}

  double GetArea() const {
    return w_ * h_;
  }
 private:
  double w_;
  double h_;
};

class Circle  {
    
 public:
  Circle(double radius) : radius_(radius) {}
  
  double GetArea() const {
    return 3.14 * radius_ * radius_;
  }
 private:
  double radius_;
};

int main() {
  Square s{1.0};
  Rectangle r{1.0, 2.0};
  Circle c{3.0};

  
  Area area;
  area.Add(&s);
  area.Add(&r);
  area.Add(&c);

  area.Print();

  return 0;
}

这就是本节主题类型擦除的完整内容~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能架构探索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 从一个示例开始
    • 接口类
      • 派生类
      • 方案一
      • 方案二
      • 终极方案
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档