Type erasure is a powerful technique in C++ that allows you to hide the concrete type of an object behind a common interface. This enables you to work with objects of different types through a unified API, promoting flexibility and reducing coupling in your code. Type erasure is particularly useful when dealing with heterogeneous collections or when you want to decouple interfaces from implementations.
#include <iostream>
#include <functional>
#include <vector>
class TypeErasedPrintable {
private:
std::function<void()> m_print_func;
public:
template<typename T>
TypeErasedPrintable(const T& obj) : m_print_func([obj]() { std::cout << obj << std::endl; }) {}
void print() const {
m_print_func();
}
};
int main() {
std::vector<TypeErasedPrintable> objects;
objects.emplace_back(42);
objects.emplace_back(3.14);
objects.emplace_back("Hello, Type Erasure!");
for (const auto& obj : objects) {
obj.print();
}
return 0;
}
This example demonstrates a basic implementation of type erasure using std::function
:
TypeErasedPrintable
class uses std::function
to store a callable object that prints the wrapped value.std::cout
.print()
method invokes the stored function, printing the object.main()
, we create a vector of TypeErasedPrintable
objects, storing integers, floating-point numbers, and strings.#include <iostream>
#include <memory>
#include <vector>
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
};
template<typename T>
class ShapeModel : public Shape {
private:
T m_shape;
public:
ShapeModel(const T& shape) : m_shape(shape) {}
void draw() const override {
m_shape.draw();
}
};
class Circle {
public:
void draw() const { std::cout << "Drawing a circle" << std::endl; }
};
class Square {
public:
void draw() const { std::cout << "Drawing a square" << std::endl; }
};
class TypeErasedShape {
private:
std::unique_ptr<Shape> m_shape;
public:
template<typename T>
TypeErasedShape(const T& shape) : m_shape(std::make_unique<ShapeModel<T>>(shape)) {}
void draw() const {
m_shape->draw();
}
};
int main() {
std::vector<TypeErasedShape> shapes;
shapes.emplace_back(Circle{});
shapes.emplace_back(Square{});
for (const auto& shape : shapes) {
shape.draw();
}
return 0;
}
This example shows a more advanced type erasure technique using virtual functions:
Shape
base class with a pure virtual draw()
method.ShapeModel
class is a template that inherits from Shape
and wraps any type that has a draw()
method.Circle
and Square
are concrete classes with their own draw()
implementations.TypeErasedShape
uses type erasure to store any shape type that conforms to the Shape
interface.main()
, we create a vector of TypeErasedShape
objects, storing different shape types.#include <iostream>
#include <memory>
#include <vector>
#include <string>
class AnimalConcept {
public:
virtual ~AnimalConcept() = default;
virtual void make_sound() const = 0;
virtual std::unique_ptr<AnimalConcept> clone() const = 0;
};
template<typename T>
class AnimalModel : public AnimalConcept {
private:
T m_animal;
public:
AnimalModel(const T& animal) : m_animal(animal) {}
void make_sound() const override {
m_animal.make_sound();
}
std::unique_ptr<AnimalConcept> clone() const override {
return std::make_unique<AnimalModel>(*this);
}
};
class Animal {
private:
std::unique_ptr<AnimalConcept> m_concept;
public:
template<typename T>
Animal(const T& animal) : m_concept(std::make_unique<AnimalModel<T>>(animal)) {}
Animal(const Animal& other) : m_concept(other.m_concept->clone()) {}
Animal& operator=(const Animal& other) {
m_concept = other.m_concept->clone();
return *this;
}
void make_sound() const {
m_concept->make_sound();
}
};
struct Dog {
void make_sound() const { std::cout << "Woof!" << std::endl; }
};
struct Cat {
void make_sound() const { std::cout << "Meow!" << std::endl; }
};
int main() {
std::vector<Animal> animals;
animals.emplace_back(Dog{});
animals.emplace_back(Cat{});
for (const auto& animal : animals) {
animal.make_sound();
}
return 0;
}
This example demonstrates type erasure with external polymorphism:
AnimalConcept
base class with pure virtual methods for the desired interface.AnimalModel
is a template class that adapts any type to the AnimalConcept
interface.Animal
class uses type erasure to store any animal type that conforms to the required interface.Animal
using the clone method.Dog
and Cat
are simple structs with a make_sound()
method.main()
, we create a vector of Animal
objects, storing different animal types.make_sound()
on all animals without knowing their concrete types.Type erasure is a powerful technique in C++ that allows you to work with objects of different types through a common interface. It provides a way to achieve polymorphism without inheritance, enabling more flexible and decoupled designs.
Key points about type erasure:
std::function
, virtual functions, or external polymorphism.While type erasure adds some runtime overhead and complexity, it provides significant benefits in terms of flexibility and design. It's a valuable tool in the C++ programmer's toolkit, especially when dealing with complex systems or libraries that need to work with various types without tightly coupling to them.