Interface in C++
Contents
1 Interface In C++
1.1 问题提出
我记得我不止一次提到说,我更喜欢 golang 的泛型设计。一个优秀的泛型系统,我希望是来表示一个方法可以接受什么。应该是一个类似于 concept 的概念。我们都知道,在 C++ 里面,我们更多的使用虚函数来实现这个功能,就像下面这样:
struct IPerson {
virtual std::string Name() = 0;
virtual uint32_t Age() = 0;
};
我们搞了一个几个纯虚函数来表示一个接口类,然后我们都会搞一些类来继承这个类,就像下面这样:
struct Student : public IPerson {
std::string Name() override;
uint32_t Age() override;
};
那在使用的地方:
void Foo(IPerson*);
这已经是我们一般在写代码时候的常规做法了,但是在这样的情况下,我们要求了所有使用这个的地方,都只能使用指针或者引用。因为我们不能对一个对象来搞这些东西。
再回想一下,golang 的泛型的样子,我们有没有一个办法,可以搞一个类似于 interface
的东西来让一个对象也可以表示这些东西呢?
1.2 简单思考1
基于上面的问题,考虑这个问题的背后是表示的是个什么类型的多态问题。Ok,显然是个运行时多态。编译时多态的问题可以配合 template
, constexpr
来解决。那么运行时多态在原本的 C++ 是通过虚函数来解决的。虚函数的实现,又是通过一个虚函数表来实现的。那么问题来了,我们可不可以自己来维护一个虚函数表来达到我们想要的效果呢?
上面我们需要的接口类,显然我们可以提炼出来一个这样的虚函数表:
struct vtable {
std::string (*Name)(void*);
uint32_t (*Age)(void*);
};
这个虚函数表表示了这个接口需要哪些接口,在这里使用 void*
来表示任意类型的指针。
那有了这个虚函数表之后,我们应该怎么使用这个呢?就像这个这样:
template<typename T>
vtable vtable_for = {
[](void* p) {
return static_cast<T*>(p)->Name();
},
[](void*) {
return static_cast<T*>(p)->Age();
},
};
这里用了 C++14 的新特性:变量模板,来构造了一个静态的全局变量,来表示对应的制定类型的虚函数表实现。 在有了上面两个东西的基础上,就得到了接口类的实现:
struct Person {
template<typename T>
Person(T t) : vtable_( &vtable_for<T> ), p(new T{t}) {}
private:
vtable* vtable_;
void* p;
};
接下来,这个类就可以很棒了。你可以像下面这么定义:
std::vector<Person> persons;
persons.push_back(Student{});
persons.push_back(Teacher{});
...
用起来的时候,一切都看起来和 golang 的那个版本差不多了呢。
Reference: