c++
多态在本文被作者片面的解释为根据运行条件不同,选择不同的对象,执行同名但实现有差别的函数。如下代码所示。
class base_algorithm {
public:
base_algorithm() = default;
~base_algorithm() = default;
public:
virtual const char* signature() {
return typeid(*this).name();
}
virtual void apply() {
std::cout << "ur own base algorithm implementation..." << std::endl;
}
};
class derived_algorithm {
public:
derived_algorithm() = default;
~derived_algorithm() = default;
public:
virtual const char* signature() {
return typeid(*this).name();
}
virtual void apply() {
std::cout << "ur own derived algorithm implementation..." << std::endl;
}
};
关于基类base_algorithm
的具体实现,你仍然可以把其中的apply
与signature
函数设置为纯虚函数。一般情况下,我会使用如std::map<std::string, base_algorithm*>
(为了方便此处写为普通指针,项目使用中也可使用智能指针std::shared_ptr<base_algorithm>
)管理基类与继承类时候往往可以带来很多方便,有一点潜在的问题是,由于平台差异,typeid(*this).name()
可能会返回不同的信息,在msvc
编译器返回的就是class base_algorithm
,这就需要根据不同的平台进行不同的parse
。如果将signature
函数的实现换为
virtual size_t signature() {
return typeid(*this).hash_code();
}
相应的,存储容器也要换为std::map<size_t, base_algorithm*>
,这样子可以省去针对不同平台写不同的parser
。
如上篇博文所说,当使用这个模式对函数进行管理的时候,会有比较大的问题,当函数接口即除了函数名字以外的签名完全相同的时候,我们可以很方便的定义一个map
对象,其·key·为函数名字的hash_code
,对应的键值为std::function
包装的函数签名。在类的signature
函数中使用hash_code
是比较方便的,我之前使用过一段时间的函数模板非类型参数签名方法,根据不同派生类的名字不同进行相应的编码,当然要在类的定义上加入一个非类型的模板参数,一般来说,类的名字没有太长的话,使用int
足够了。
template<int _signature>
void specifical_function {
std::cout << "specifical_function ..." << std::endl;
}
对函数签名的计算要在编译阶段完成,所以我就使用了模板元编程的计算法则,通过将char
类型的数据转换成int
类型。具体实现如下,现在看来这些实现确实有些笨重,低效。如果必须使用这种签名的话,现在使用泛型lambda
可以轻松优美的完成这个任务。
template<char...>
struct _signature_type {
};
template<typename>
struct _signature {
using type = std::void_t<>;
static const int value = -1;
};
template<>
struct _signature<_signature_type<>> {
using type = std::void_t<>;
static const int value = 0;
};
template<char _>
struct _signature<_signature_type<_>> {
using type = _signature_type<_>;
static const int value = int(_);
};
template<char _1st, char _2nd>
struct _signature<_signature_type<_1st, _2nd>> {
using type = _signature_type<_1st, _2nd>;
static const int value = int(((int)_1st << 1) + (int)_2nd);
};
template<char _1st, char _2nd, char... _>
struct _signature<_signature_type<_1st, _2nd, _...>> {
using type = _signature_type<_1st, _2nd, _...>;
static const int value = _signature<_signature_type<_1st, _2nd>>::value + _signature<_signature_type<_...>>::value;
};
template<char... _>
auto signature_v = _signature<_signature_type<_...>>::value;
当定义函数的时候,可以使用如下的方式加入数字签名。
template<int = signature_v<'s','p','e','c','i','f','i','c','a','l','_','f','u','n','c','t','i','o','n'>
void specifical_function () {
...
}
好丑啊,当时怎么想出来这个奇葩的方法了啊,不懂当时脑子怎么想的。这个只是明确了函数的单一的签名问题,但当函数的参数不完全相同的时候,就比较难处理,引用上篇博文的叙述:虽然有些操作的物理意义接近或基本相同,但却并不一定所有的函数都有同样的输入参数,当然如果差距不大的话,可以加入一些 dummy parameter,有些情况,输入参数差别太大的时候,强行加入参数会导致代码极其丑陋
。我一开始的思路是将lambda
函数作为一个函数包装器,将不同参数的函数包装进一个相同的lambda
内。
void parameter_1(int) {
std::cout << "void(int)" << std::endl;
}
void parameter_2(int, int) {
std::cout << "void(int, int)" << std::endl;
}
auto lambda_1 = [&]() {
return std::function<void(int)>(std::bind(parameter_1, std::placeholders::_1));
};
auto lambda_2 = [&]() {
return std::function<void(int, int)>(std::bind(parameter_2, std::placeholders::_1, std::placeholders::_2));
};
虽然通过lambda
的包装,可以通过void
参数获取不同的std::function
对象,但是由于lambda
函数返回的类型不同,还是没有办法存储进一个统一的std::map
中去。确实比较棘手。往下的话,我仍然想要把这些不同的lambda
存储进一个容器中,想来想去只有std::tuple
这个东西可以完成任务(好像还有点印象是linux中有异质链表完成了存储不同基础数据类型的,但一时又想不起具体实现方法)
。
auto t = make_tuple(lambda_1, lambda_2);
auto v_i = std::get<0>(t);
auto v_i_i = std::get<1>(t);
v_i()(0);
v_i_i()(0, 0);
这样子,总算是将这些个不同的函数都存储进了一个地方,最后就是将我们获取的调用不同参数的key
值与我们获取std::tuple
中std::function
对象的int
模板参数做一个映射,这里的key
可能是个enum class
也可能是一个std::string
,甚至仅仅是一个int
数值,到这里算是完成了任务。不过回头想下,废了半天劲,这个样子跟直接将不同参数的原始函数存储进std::tuple
之中好像没有一丝丝先进的意味,尴尬。但是肯定有更加优美的方案完成这个任务的,容我想想,回头再更。
待续……