一:概述
C++ 中的类型擦除(Type Erasure)是一种技术,允许你在不暴露具体类型信息的情况下,通过统一的接口处理不同的类型。这种技术常用于实现泛型编程,特别是在需要支持多种不同类型的情况下,如容器、算法和接口。
类型擦除通过隐藏类型信息,允许程序在运行时处理不同的类型。通常,这种技术涉及使用基类指针或模板来实现一种抽象,使得具体类型的细节在使用时被“擦除”。
二:示例:
#include <iostream>
#include <memory>
#include <vector>
#include <functional>
// 抽象基类
class Any {
public:
virtual ~Any() = default;
virtual void call() const = 0; // 虚函数
};
// 模板派生类
template <typename T>
class AnyImpl : public Any {
public:
AnyImpl(T value) : value_(value) {}
void call() const override {
value_(); // 调用存储的函数
}
private:
T value_;
};
// 类型擦除容器
class FunctionContainer {
public:
template <typename T>
void add(T func) {
functions_.emplace_back(std::make_shared<AnyImpl<T>>(func));
}
void execute() const {
for (const auto& func : functions_) {
func->call(); // 调用每个函数
}
}
private:
std::vector<std::shared_ptr<Any>> functions_;
};
// 测试
void hello() {
std::cout << "Hello, World!" << std::endl;
}
void goodbye() {
std::cout << "Goodbye, World!" << std::endl;
}
int main() {
FunctionContainer container;
container.add(hello);
container.add(goodbye);
container.execute(); // Output: Hello, World! Goodbye, World!
return 0;
}
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Object {
public:
template <typename T>
explicit Object(const T& obj): object(std::make_shared<Model<T>>(std::move(obj))){}
std::string getName() const {
return object->getName();
}
struct Concept {
virtual ~Concept() {}
virtual std::string getName() const = 0;
};
template< typename T >
struct Model : Concept {
explicit Model(const T& t) : object(t) {}
std::string getName() const override {
return object.getName();
}
private:
T object;
};
std::shared_ptr<const Concept> object;
};
void printName(std::vector<Object> vec){
for (auto v: vec) std::cout << v.getName() << '\n';
}
struct Bar{
std::string getName() const {
return "Bar";
}
};
struct Foo{
std::string getName() const {
return "Foo";
}
};
int main(){
std::cout << '\n';
std::vector<Object> vec{Object(Foo()), Object(Bar())};
printName(vec);
std::cout << '\n';
}
三:C++ 标准库中的类型擦除:
C++ 标准库中有一些使用类型擦除的例子,如 std::function
和 std::any
:
std::function
:可以存储任意可调用对象(函数、lambda、绑定表达式等),并提供统一的调用接口。std::any
:可以存储任意类型的值,同时提供类型安全的访问接口。
#include <any>
#include <iostream>
int main()
{
std::cout << std::boolalpha;
// any type
std::any a = 1;
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
a = 3.14;
std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
a = true;
std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
// bad cast
try
{
a = 1;
std::cout << std::any_cast<float>(a) << '\n';
}
catch (const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
// has value
a = 2;
if (a.has_value())
std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
// reset
a.reset();
if (!a.has_value())
std::cout << "no value\n";
// pointer to contained data
a = 3;
int* i = std::any_cast<int>(&a);
std::cout << *i << '\n';
}
#include <functional>
#include <iostream>
struct Foo
{
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_ + i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum
{
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// store a free function
std::function<void(int)> f_display = print_num;
f_display(-9);
// store a lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// store the result of a call to std::bind
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
// store a call to a member function
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// store a call to a data member accessor
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// store a call to a member function and object
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
f_add_display2(2);
// store a call to a member function and object ptr
std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
f_add_display3(3);
// store a call to a function object
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
auto factorial = [](int n)
{
// store a lambda object to emulate "recursive lambda"; aware of extra overhead
std::function<int(int)> fac = [&](int n) { return (n < 2) ? 1 : n * fac(n - 1); };
// note that "auto fac = [&](int n) {...};" does not work in recursive calls
return fac(n);
};
for (int i{5}; i != 8; ++i)
std::cout << i << "! = " << factorial(i) << "; ";
std::cout << '\n';
}