使用基于概念的多态,重点是使用值语义和类型擦除来实现类型多态,如何实现多重继承?
界面示例如下所示。它定义了一个可序列化的类型,以要求具有适当签名的函数序列化和大小。
#include <iostream>
#include <memory>
using namespace std;
class Serializable {
public:
template <typename T>
Serializable(T x) : self_(new model<T>(move(x)))
{ }
friend size_t serialize(const Serializable& x, uint8_t* buffer, size_t bufferSize) {
x.self_->serialize_(buffer, bufferSize);
}
friend size_t size(const Serializable& x) {
x.self_->size_();
}
private:
struct Concept {
virtual ~Concept() = default;
virtual size_t serialize_(uint8_t*, size_t bufferSize) const = 0;
virtual size_t size_() const = 0;
};
template <typename T>
struct model : Concept {
model(T x) : data_(move(x)) { }
size_t serialize_(uint8_t* buffer, size_t bufferSize) const
{ serialize(data_, buffer, bufferSize); }
size_t size_() const
{ size(data_); }
T data_;
};
shared_ptr<const Concept> self_;
};
然后实现接口的类如下所示。它没有直接继承纯虚函数,而是使用相同的函数名。MyClassB 后来使用并且是相同的实现,只是名称不同。
class MyClassA {
public:
MyClassA(const string& name) :
name_(name)
{ }
friend size_t serialize(const MyClassA& x, uint8_t* buffer, size_t bufferSize);
friend size_t size(const MyClassA& x);
private:
const string name_;
};
size_t serialize(const MyClassA& x, uint8_t* buffer, size_t bufferSize) {
size_t bytesToWrite = size(x);
if(bytesToWrite <= bufferSize) {
memcpy(buffer, x.name_.c_str(), bytesToWrite);
} else {
bytesToWrite = 0;
}
return bytesToWrite;
}
size_t size(const MyClassA& x) {
x.name_.length();
}
此外,为向量创建了相同的签名。
size_t size(const vector<Serializable>& x) {
size_t totalSize = 0;
for (auto& e : x) {
totalSize += size(e);
}
return totalSize;
}
size_t serialize(const vector<Serializable>& x, uint8_t* buffer, size_t bufferSize) {
size_t bytesToWrite = size(x);
size_t offset = 0;
if(bytesToWrite <= bufferSize) {
for (auto& e : x) {
offset += serialize(e, buffer + offset, bufferSize - offset);
}
} else {
bytesToWrite = 0;
}
return bytesToWrite;
}
然后使用类型、MyClassA、MyClassB 和向量,下面的客户端代码创建对象,将它们添加到向量中,然后打印它们。
int main() {
vector<Serializable> channel;
uint8_t buffer[30] = {};
MyClassA myClassA("Hello World!");
serialize(myClassA, buffer, sizeof buffer);
cout << buffer << endl;
cout << "-------------------" << endl;
channel.emplace_back(MyClassA(" Apples"));
channel.emplace_back(MyClassB(" Oranges"));
serialize(channel, buffer, sizeof buffer);
cout << buffer << endl;
cout << "-------------------" << endl;
cout << size(channel) << endl;
cout << "-------------------" << endl;
vector<Serializable> channel2;
channel2.emplace_back(channel);
channel2.emplace_back(channel);
serialize(channel2, buffer, sizeof buffer);
cout << buffer << endl;
}
代码已在以下存储库中准备好。在根目录中构建运行 scons:https : //github.com/moritz89/type-erasure-test? files =1
回到最初的问题,是否可以创建额外的接口,例如 ReportError 或 Client,来定义必须实现的功能,并且可以统一创建多继承结构?
你可以这样做,但你确实需要稍微改变你的方法,这样我们就不会不必要地重复。首先,我们需要一个简单的empty_model
类:
template <typename T, typename ... <class> Models>
struct empty_model : Models<empty_model> {
model(T x) : data_(move(x)) { }
T data_;
};
这仅包含为T
. 接下来,我们必须通过 CRTP将我们的每个概念模型变成只处理转发接口而不是所有权的东西:
// Still nested in serializable, or whatever concept
template <typename D>
struct model : Concept {
size_t serialize_(uint8_t* buffer, size_t bufferSize) const
{ serialize(static_cast<const D&>(*this).data_, buffer, bufferSize); }
size_t size_() const
{ size(static_cast<const D&>(*this).data_); }
};
所以,对于每个概念,我们编写的嵌套model
类有点不同,但到目前为止样板的数量并没有更大。我们现在使用 typedef 来创建我们想要的实际模型:
template <class T>
using serializable_model = empty_model<T, Serializable::model>;
template <class T>
using my_model = empty_model<T, Serializable::Model, ReportError::Model>;
我们需要做的最后一件事是改变概念以拥有一个接受实际的构造函数shared_ptr
:
Serializable(shared_ptr<const Concept> x) : self_(move(x)) {}
因此,原则上您现在可以编写一个具体的类Foo
,并执行以下操作:
auto f = make_shared<my_model<Foo>>(Foo{});
请记住(是的,它已经变得很难......)my_model
将从嵌套模型类(适当模板化)继承,这些类继承自基本概念。这意味着它my_model<Foo>
是 的孙子Serializable::Concept
,因此f
将隐式转换,以便您可以执行以下操作:
Serializable s(f);
所有这一切都说:虽然肖恩父母的演讲很精彩(是的,很容易识别),但这是很多工作、复杂性和样板。根据我的经验,在 99% 的实际代码中,您最好只是注销接口,然后编写直接从它们继承的具体类型。在进入这个兔子洞之前,我会确保您的特定问题真的从中受益。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句