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.