Understanding Type Erasure in C++ for Polymorphic Design
This article explains the concept of type erasure in C++, demonstrating how to achieve polymorphic behavior without a common base class by using wrapper classes, templates, and container storage, and provides multiple implementation approaches with complete code examples.
Type erasure is a C++ programming technique that enables polymorphic operations without sacrificing performance or adding unnecessary runtime overhead. By hiding the concrete type of objects behind a uniform interface, it allows different types to be handled polymorphically while deferring type resolution to runtime.
The article starts with a traditional virtual‑function based polymorphism example using an abstract Shape interface and concrete Square , Rectangle , and Circle classes. It shows how a vector of Shape* can store heterogeneous objects and invoke GetArea() uniformly.
class Shape {
public:
virtual double GetArea() const = 0;
virtual ~Shape() {}
};
class Square : public Shape {
public:
Square(double side) : side_(side) {}
double GetArea() const override { return side_ * side_; }
private:
double side_;
};
class Rectangle : public Shape {
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double GetArea() const override { return w_ * h_; }
private:
double w_, h_;
};
class Circle : public Shape {
public:
Circle(double r) : radius_(r) {}
double GetArea() const override { return 3.14 * radius_ * radius_; }
private:
double radius_;
};
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{&s, &r, &c};
for (auto&& elem : shape) {
std::cout << elem->GetArea() << std::endl;
}
return 0;
}When the three concrete classes have no common base, the article poses the question of how to store them in a container. It then presents three solutions based on type erasure.
Solution 1 – Forced Common Base
Introduce a new abstract base MyShape and wrap each concrete class in a derived wrapper that holds a std::unique_ptr to the original object.
class MyShape {
public:
virtual double GetArea() const = 0;
virtual ~MyShape() {}
};
class MySquare : public MyShape {
public:
MySquare(double side) : square_(std::make_unique
(side)) {}
double GetArea() const override { return square_->GetArea(); }
private:
std::unique_ptr
square_;
};
class MyRectangle : public MyShape {
public:
MyRectangle(double w, double h) : rectangle_(std::make_unique
(w, h)) {}
double GetArea() const override { return rectangle_->GetArea(); }
private:
std::unique_ptr
rectangle_;
};
class MyCircle : public MyShape {
public:
MyCircle(double r) : circle_(std::make_unique
(r)) {}
double GetArea() const override { return circle_->GetArea(); }
private:
std::unique_ptr
circle_;
};
int main() {
MySquare s{1.0};
MyRectangle r{1.0, 2.0};
MyCircle c{3.0};
std::vector
shape{&s, &r, &c};
for (auto&& elem : shape) {
std::cout << elem->GetArea() << std::endl;
}
return 0;
}Solution 2 – Template Wrapper
Define a generic Wrapper<T> that inherits from MyShape and forwards GetArea() to the wrapped object. This eliminates the need to write a separate wrapper class for each concrete type.
template
class Wrapper : public MyShape {
public:
Wrapper(T* t) : t_(t) {}
double GetArea() const override { return t_->GetArea(); }
private:
T* t_ = nullptr;
};
int main() {
Square s{1.0};
Rectangle r{1.0, 2.0};
Circle c{3.0};
std::vector
shape{
new Wrapper
(&s),
new Wrapper
(&r),
new Wrapper
(&c)
};
for (auto&& elem : shape) {
std::cout << elem->GetArea() << std::endl;
}
return 0;
}Solution 3 – Ultimate Area Manager
A higher‑level Area class encapsulates the type‑erasure mechanism. It provides Add to store any shape via the Wrapper and Print to output all stored areas, handling memory cleanup in its destructor.
class Area {
public:
template
void Add(T* shape) {
shape_.emplace_back(new Wrapper
(shape));
}
void Print() {
for (auto&& elem : shape_) {
std::cout << elem->GetArea() << "\n";
}
}
~Area() {
for (auto&& elem : shape_) delete elem;
}
private:
class MyShape {
public:
virtual double GetArea() const = 0;
virtual ~MyShape() {}
};
template
class Wrapper : public MyShape {
public:
Wrapper(T* t) : t_(t) {}
double GetArea() const override { return t_->GetArea(); }
private:
T* t_ = nullptr;
};
std::vector
shape_;
};
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;
}All three approaches illustrate how type erasure can be used in C++ to store heterogeneous objects in a single container and invoke a common operation without requiring a shared inheritance hierarchy.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.