类型擦除
题目描述
众所周知,Lambda 表达式的类型名是无法获取的,每个 Lambda 都拥有独一无二的类型。因此我们处理 Lambda 表达式时大多使用模板。但是在很多场合模板并不适用,我们希望能有一个通用的“类型”来接纳所有的、可以调用的函数对象。
这就需要用到类型擦除技术。类型擦除主要依赖于多态和模板两个语法。
接下来我们将编写一个 Function
类。它可以用任何类型的对象初始化,只要它可以被无参地调用。这样初始化后,该 Function
对象就 包装 了原始的可调用对象。同时,Function
类也实现 operator()
,从而调用其内部被包装的真正的可调用对象。
// 下面两行的 Lambda 表达式类型不同,但都可以包装到 Function 内部
Function f1 = [=] () { std::printf("f1"); };
Function f2 = [=] () { std::printf("f2"); };
那么如何实现 Function
呢?首先很显然,它有一个构造函数模板,以接受来自任意类型对象的初始化。此外,Function
中有一个 Callable*
类型的指针(稍后展开)。在上述构造函数中,若形参名为 f
,具有 F
类型;那么该指针将用 new FuncWrapper<F>(f)
初始化。
虽然我们还不知道 Callable
和 FuncWrapper<F>
是什么;但是从上文的描述来看,可以知道它们的指针之间能够转换。是的,可以猜到 FuncWrapper<F>
是 Callable
的派生类。不仅如此,它们还是多态的。
Callable
基类是抽象类。它有一个接口 void call();
。FuncWrapper
是类模板:它存储一份 F
类型的对象成员 F f;
。它在实现 call
接口时,调用成员 f
。
在 Function
的 operator()
中,调用基类 Callable
的 call
接口。由于这个接口是多态的,所以它会调用到 FuncWrapper<F>
的实现上;而后者就直接调用 F
类型对象的 ()
运算符。这就实现了用非模板、非泛型的 Function
类,包装任意类型的可调用对象。
请根据上述描述,实现 Function
。
关于输入
无
关于输出
见样例输出
参考答案
#include <iostream>
class Callable {
public:
virtual void call() = 0;
virtual ~Callable() = default;
};
template <typename T>
class FuncWrapper : public Callable {
T f;
public:
FuncWrapper(T f) : f(f) {}
void call() override {
f();
}
};
class Function {
Callable* p;
public:
Function() : p(nullptr) {}
template <typename T>
Function(T f) : p(new FuncWrapper<T>(f)) {}
void operator()() {
p->call();
}
~Function() {
delete p;
}
};
void f1() {
std::cout << "f1" << std::endl;
}
struct F2 {
void operator()() {
std::cout << "f2" << std::endl;
}
};
F2 f2;
int main() {
int v = 3;
auto f3 = [&]() {
std::cout << "f" << v << std::endl;
};
Function funcs[]{f1, f2, f3};
for (int i = 0; i < 3; i++) {
funcs[i]();
}
}