深入浅出 C++ 类型擦除

科技   2024-05-27 08:18   浙江  

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

写在前面

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

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

从一个示例开始

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

接口类

示例代码如下:

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

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

派生类

代码如下:

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。

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

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

int main() {
  Square s{1.0};
  Rectangle r{1.02.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,输出结果显而易见:

1
2
28.26

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

std::vector<???> shape{&s, &r, &c};

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

方案一

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

共同基类如下:

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

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

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_;
};

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

int main() {
  MySquare s{1.0};
  MyRectangle r{1.02.0};
  MyCircle c{3.0};
  std::vector<MyShape*> shape{&s, &r, &c};
  
  for (auto &&elem : shape) {
    std::cout << elem->GetArea() << std::endl;
  }
  
  return 0;
}

就不再赘述了~

完整代码如下:

#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.02.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,工作量难以想象,在此,可以采用模板方式来解决此类问题,如下:

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

使用方式则如下:

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

int main() {
  Square s{1.0};
  Rectangle r{1.02.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;
}

完整的代码形如:

#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.02.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;
}

终极方案

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

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()分别用以添加对象和输出。

完整的示例代码如下:

#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.02.0};
  Circle c{3.0};

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

  area.Print();

  return 0;
}

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

推荐阅读  点击标题可跳转

1、性能大杀器:c++中的copy elision

2、性能大杀器:std::move 和 std::forward

3、Configuring Transitive Dependencies with Modern CMake

CPP开发者
我们在 Github 维护着 9000+ star 的C语言/C++开发资源。日常分享 C语言 和 C++ 开发相关技术文章,每篇文章都经过精心筛选,一篇文章讲透一个知识点,让读者读有所获~
 最新文章