Do Runtime polymorphism correctly

C++的运行时多态历史上使用继承和虚函数的方法,可是继承有很多缺点,比如基类和派生类紧密耦合,基类的成员变量和成员函数都介入到了派生类。sean parent在其better code系列演讲中对继承做了强烈批判。louis的dyno库使用boost hana为基础,并使用type erase技术对运行时多态做了非常杰出的改进。这里我们介绍google 工程师john bandela 的另外一个方案metaprogrammed_polymorphism,使用type erase和函数重载技术,不过非常小巧,不到270行。

基本思想是过去的虚函数, 这里改为使用自由函数,虚函数的名字改为自由函数的第一个参数的类别名称。 比如接口

class Base{
public:
virtual void draw(std::ostream&)const;
void x2(std::unique_ptr<int>);
};

改为

class draw {};
class x2 {};

// Use types instead of names
// void draw(std::ostream&) -> void(draw, std::ostream&)
void call_draw(polymorphic::ref<void(draw, std::ostream&) const> d) {
	std::cout << "in call_draw\n";
	d.call<draw>(std::cout);
}


template <typename T>
void poly_extend(draw, const T& t, std::ostream& os) {
	os << t << "\n";
}

template <typename T>
void poly_extend(x2, T& t, std::unique_ptr<int>) {
	t = t + t;
}


	polymorphic::object<
		void(x2,std::unique_ptr<int>),
		void(draw, std::ostream & os) const
		> object;

poly_extend的第二个参数T可以看作以前的派生类。第一个参数是和以前虚函数名字相同的类型。 如果要实现针对某种类型特定的操作,只要重载poly_extend就可,比照以前派生类中覆盖虚函数。 如

void poly_extend(draw, const int& t, std::ostream& os) {
	os<<"int:" << t << "\n";
}

polymorphic::object提供多态的value语义,也就是可以方便的放在容器里,polymorphic::ref提供多态的ref语义,用作函数参数,类似view。

具体使用方法,object.emplace用于创建的满足object的模板参数的某种特定对象,如object.emplace(9)代表object是int 对象的wrapper。object.call<>用于调用具体的poly_extend函数,如object.call <draw>(3)将调用draw类型为第一个参数类型的如下函数

template <typename T>
void poly_extend(draw, const T& t, std::ostream& os) {
	os << t << "\n";
}

完整的例子如下,

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// limitations under the License.

#include "polymorphic.hpp"

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class draw {};
class x2 {};

// Use types instead of names
// void draw(std::ostream&) -> void(draw, std::ostream&)
void call_draw(polymorphic::ref<void(draw, std::ostream&) const> d) {
	std::cout << "in call_draw\n";
	d.call<draw>(std::cout);
}


template <typename T>
void poly_extend(draw, const T& t, std::ostream& os) {
	os << t << "\n";
}

class x2;
template <typename T>
void poly_extend(x2, T& t, std::unique_ptr<int>) {
	t = t + t;
}

int main() {
	std::vector<polymorphic::object<
		void(x2,std::unique_ptr<int>),
		void(draw, std::ostream & os) const
		>> objects;
	for (int i = 0; i < 30; ++i) {
		switch (i % 3) {
		case 0:
			objects.emplace_back(i);
			break;
		case 1:
			objects.emplace_back(double(i) + double(i) / 10.0);
			break;
		case 2:
			objects.emplace_back(std::to_string(i) + " string");
			break;
		}
	}
	auto o = objects.front();
	polymorphic::object<void(draw, std::ostream&)const> co(10);
	auto co1 = co;
	call_draw(co);
	for (const auto& o : objects) call_draw(o);
	for (auto& o : objects) o.call<x2>(nullptr);
	for (auto& o : objects) call_draw(o);
}
Posted 2021-10-21