此文档参考自:https://gracicot.github.io/conceptmodel/2017/09/13/concept-model-part1.html ,觉得很有趣,就翻译过来了

一、Concept-Model:多态的新视角

面向对象编程大家都很熟悉,只需实现一个接口 Interface 。但这种使用经典 OOP 实现的多态性是侵入性的,即使在真正不需要的地方也会强制多态行为,比如总会触发堆分配,伴随着一个包含基类的列表。今天我们要介绍一种新的概念模型( Runtime ConceptVirtual Concept ),这是一种可能会改变你对多态性及其试图解决的问题的看法的模型。

让我们从一个简单的例子开始:样例中有一个接口 abstract_task ,一个或多个实现(即 print_task ),一个类型擦除列表 tasks,以多态方式执行该 process 函数。

// Our interface.
struct abstract_task {
virtual void process() = 0;
virtual ~abstract_task() = default;
}; // An implementation of our interface.
struct print_task : abstract_task {
void process() override {
std::cout << "Print Task" << std::endl;
}
}; // A type erased list of tasks.
std::vector<std::unique_ptr<abstract_task>> tasks; // A function that push a new task in the list.
void push(std::unique_ptr<abstract_task> task) {
tasks.emplace_back(std::move(task));
} // execute all tasks and clear the list.
void run() {
for(auto&& task : tasks) {
task->process();
} tasks.clear();
}

上面的代码符合我们大部分的编程直觉。首先,这里需要动态分配,且没有办法解决它。但真实意图不是“我们想要 100% 的时间进行动态分配!” ,真实意图是“我们想要一个类型擦除的任务列表”。然而,始终通过动态分配和多态恰好是最常见的方式,而且它也是语言自动实现多态的唯一方式。

其次,它不适用于所有类,很多人可能会说:

是的,只需实现该接口即可!所有类型都有效!

问题是,并非所有类型都可以继承 abstract_task 。假设有这样一个类:

struct some_library_task : library_task {
void process() { /* ... */ }
};

且要求你不能更改该类,你必须实现一个 Adaptor 才能使其在代码中工作。

此外,还有另一种类不可能扩展接口:lambdas 。是的,lambda !你的代码可能与他们不兼容!想象一下写这样的东西:

push([] { std::cout << "print something!"; });

遗憾的是,这行不通,因为 lambda 不是动态分配的,也不会扩展类 abstract_task

Concept-Model 惯用法旨在解决这些问题,我们来具体是怎么实现的。

二、Concept-Model: The adapter pattern on steroids

在本节中,我们将解释从经典 OOP 到 Concept-Model 的迁移过程。将会分解为许多小步骤,以便更容易理解。

首先,函数 push 接受一个 std::unique_ptr。想象一下,假如你有几十个函数以这种方式执行任务,如果有一天你需要所有那些采用 std::unique_ptr<abstract_task> 原始指针或引用的函数怎么办?我们先从这一点入手:取而代之的是包含指针的结构:

struct task {
task(std::unique_ptr<abstract_task> t) noexcept : wrapped{std::move(t)} {} std::unique_ptr<abstract_task> wrapped;
}; // A vector of task, which wrap a unique pointer.
std::vector<task> tasks; // We take a task by value, since it's constructible from a unique pointer.
void push(task t) {
tasks.emplace_back(std::move(t));
}

但现在还是有些问题,some_task.wrapped->process()的用法会很难受,继续调整:

struct task {
task(std::unique_ptr<abstract_task> t) noexcept : wrapped{std::move(t)} {} void process() {
wrapped->process();
} private:
std::unique_ptr<abstract_task> wrapped;
}; void run() {
for(auto&& task : tasks) {
task.process();
} tasks.clear();
}

现在已经很不错了!对于任何地方 std::unique_ptr<abstract_task> ,你都可以放到 tasks里(隐式构造),且是 pass-by-value

push(std::make_unique<print_task>());

但是等等……这并没有解决我们的问题!我们想要支持 lambda,改变对象的发送方式,避免堆分配,这真的有用吗?

当然!在该列表中,我们现在可以做一件事:改变传递对象的方式。无需更改 200 个函数签名,我们只需更改 task 的构造函数。

现在,希望 push 函数能够接收 some_library_task 。为此,我们需要一个 Adaptor 来使这些类型适应接口abstract_task

// Our adapter. We contain a library task and implementing the abstract_task interface
struct some_library_task_adapter : abstract_task {
some_library_task_adapter(some_library_task t) : task{std::move(t)} {} void process() override {
task.process();
} some_library_task task;
}; struct task {
task(std::unique_ptr<abstract_task> t) noexcept : wrapped{std::move(t)} {} // We can now receive a library task by value.
// We move it into a new instance of adapter.
task(some_library_task t) noexcept :
wrapped{std::make_unique<some_library_task_adapter>(std::move(t))} {} void process() {
wrapped->process();
} private:
std::unique_ptr<abstract_task> wrapped;
}; int main() {
// push a new task to the vector
push(some_library_task{});
}

到此,我们可以通过 pass-by-value 方式来 push 未继承 abstract_tasksome_library_task 对象。

但是,那些继承自 abstract_tasktask 还不能 pass-by-value,而必须使用 ptr。因此,我们需要将为每个类创建一个 Adaptor, 但我们不希望任何外部类扩展 abstract_task,因此它将是一个私有成员类型:

struct task {
task(some_library_task task) noexcept :
self{std::make_unique<library_model_t>(std::move(t))} {}
task(print_task task) noexcept :
self{std::make_unique<print_model_t>(std::move(t))} {}
task(some_other_task task) noexcept :
self{std::make_unique<some_other_model_t>(std::move(t))} {} void process() {
self->process();
} private:
// This is our interface, now named concept_t instead of abstract_task
struct concept_t {
virtual ~concept_t() = default;
virtual void process() = 0;
}; // We name our struct `model` instead of `adapter`
struct library_model_t : concept_t {
library_model_t(some_library_task s) noexcept : self{std::move(s)} {}
void process() override { self.process(); }
some_library_task self;
}; struct print_model_t : concept_t {
library_model_t(print_task s) noexcept : self{std::move(s)} {}
void process() override { self.process(); }
print_task self;
}; struct some_other_model_t : concept_t {
library_model_t(some_other_task s) noexcept : self{std::move(s)} {}
void process() override { self.process(); }
some_other_task self;
}; // We quite know it's wrapped. Let's name it self
std::unique_ptr<concept_t> self;
};

这太荒谬了!我们总不能为所有的 abstract_task 的派生类都复制一份构造函数,以及继承 concept_t的子类代码。

的确,C++ 中有一个很棒的工具,它经过精心设计,可以避免无意识的复制粘贴:模板!

struct task {
template<typename T>
task(T t) noexcept : self{std::make_unique<model_t<T>>(std::move(t))} {} void process() {
self->process();
} private:
struct concept_t {
virtual ~concept_t() = default;
virtual void process() = 0;
}; template<typename T>
struct model_t : concept_t {
model_t(T s) noexcept : self{std::move(s)} {}
void process() override { self.process(); }
T self;
}; std::unique_ptr<concept_t> self;
}; int main() {
// natural syntax for object construction! Yay!
push(some_library_task{});
push(my_task{});
push(print_task{});
}

问题解决了!我们代码的 API 中不再有复制粘贴,不再有继承,不再有指针!

三、Conclusion

这个 Concept-Model 是如何解决我们在开头列出的所有问题?

首先,它可以自然地应用多态性,与其他代码看起来很统一,语法也更简洁。

void do_stuff() {
// Initialize a std::string using a value in direct initialization
std::string s{"value"}; // Pretty similar syntax eh?
task t{print_task{}}; // Or if you like AAA style
auto s2 = std::string{"potato"};
auto t2 = task{print_task{}}; // use string like this
auto size = s.size(); // use task like that. Again, pretty similar
t.process();
}

没有箭头,没有 new ,没有std::make_*。所有的多态性隐藏在实现细节中,潜在的生效的。其次,它避免了堆分配。是的,即使我们在内部通过唯一指针传递我们的对象。

void do_stuff() {
some_task t; // do some stuff with task
t.stuff(); // maybe push the task
if (condition()) {
push(std::move(t));
}
}

在上面示例中,t有条件地被推入列表。如果我们不需要堆分配和多态性,我们可以在运行时决定不使用它。还有其他策略,比如使用 SBO 来避免动态分配,我将在其他部分介绍。

第三,我们的任务实现可以按照 process 自己想要的方式实现功能。例如:

struct special_task {
int process(bool more_stuff = false) const {
// ...
}
};

这仍然满足了这个概念。t.process() 即使函数是常量、接受可选参数或具有不同的返回类型,我们仍然可以调用。

C++ 中 Concept-Model 概念模型的更多相关文章

  1. 1、MVC和EF中的 Model First 和 Code First

    准备:先引入MVC和EF的dll包 *命令方法:打开工具——库程序包管理器——程序包管理器控制台,选择自己的项目 a)     Install-Package EntityFramework -Ver ...

  2. springMVC中利用model在JSTL进行回填值

    1.ringMVC中利用model回填值 后台中,利用model返回值,如 model.addAttribute("MS_info" , MS_info); 前台回填值: text ...

  3. 浅析在QtWidget中自定义Model

    Qt 4推出了一组新的item view类,它们使用model/view结构来管理数据与表示层的关系.这种结构带来的功能上的分离给了开发人员更大的弹性来定制数据项的表示,它也提供一个标准的model接 ...

  4. ASP.NET MVC中默认Model Binder绑定Action参数为List、Dictionary等集合的实例

    在实际的ASP.NET mvc项目开发中,有时会遇到一个参数是一个List.Dictionary等集合类型的情况,默认的情况ASP.NET MVC框架是怎么为我们绑定ASP.NET MVC的Actio ...

  5. Django中的Model(字段)

    Model Django中的model是用来操作数据库的,Model是一个ORM框架,我们只需要关心model的操作,而不需要关心到底是哪一种数据库. 一.基本知识: 数据库引擎: Django中自带 ...

  6. Django中的Model继承

    Django 中的 model 继承和 Python 中的类继承非常相似,只不过你要选择具体的实现方式:让父 model 拥有独立的数据库:还是让父 model 只包含基本的公共信息,而这些信息只能由 ...

  7. ASP.NET MVC4中的Model验证 移除指定验证信息

    MVC中通过Model在页面间传值使的程序开发变得更加的快捷,但是很多时候,我们在数据传递的时候为了确保数据的有效性,要对Model的相关属性做基本的数据验证. 不多说直接上个代码,Model的实体类 ...

  8. django中的Model模型一:

    在django的框架设计中采用了mtv模型,即Model,template,viewer Model相对于传统的三层或者mvc框架来说就相当对数据处理层,它主要负责与数据的交互,在使用django框架 ...

  9. ASP.NET MVC中对Model进行分步验证的解决方法

    原文:ASP.NET MVC中对Model进行分步验证的解决方法 在我之前的文章:ASP.NET MVC2.0结合WF4.0实现用户多步注册流程中将一个用户的注册分成了四步,而这四个步骤都是在完善一个 ...

  10. cakephp2.3.0 lib中的Model.php有一个bug

    1. cakephp2.3.0 lib中的Model.php有一个bug, 加上 !empty($db->config['prefix']) 这个判断更好.有时候会少进行一次 new PDO() ...

随机推荐

  1. 测试开发之系统篇-Docker常用操作

    Docker容器(Container)的运行基于镜像(image),您可以在Docker Hub上检索,或通过Dockerfile文件自己构建镜像. 首先拉取MySQL官方镜像的最新版(latest) ...

  2. 80+产品正通过兼容性测试,OpenHarmony生态蓬勃发展

    4 月 25 日,开放原子开源基金会举办了 OpenAtom OpenHarmony(以下简称"OpenHarmony")技术日活动,OpenHarmony PMC 委员代表首次对 ...

  3. LiteOS-A内核中的procfs文件系统分析

    一. procfs介绍 procfs是类UNIX操作系统中进程文件系统(process file system)的缩写,主要用于通过内核访问进程信息和系统信息,以及可以修改内核参数改变系统行为.需要注 ...

  4. WPF/MVVM模式入门教程(一):简介与规范

    引用:https://www.cnblogs.com/flh1/p/12421652.html 什么是MVVM模式? MVVM的全称是--Model.View.ViewModel,翻译过来就是:模型. ...

  5. k8s之持久存储卷PV和PVC

    一.简介 在前边文章中可以看到,Kubernetes中依赖后端存储包括:NFS.Ceph.块存储等存储设备实现数据的远程存储以及数据持久化. 使用这些网络存储资源需要工程师对存储有一定的了解,并需要在 ...

  6. Maven 三种archetype说明合集【转载】

    Maven 三种archetype说明 新建Maven project项目时,需要选择archetype. 那么,什么是archetype? archetype的意思就是模板原型的意思,原型是一个Ma ...

  7. 分布式文件存储-FastDFS

    1.1 FastDFS简介 1.1.1 FastDFS体系结构 FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了 ...

  8. ABP -Vnext框架一步一步入门落地教程——ABP Vnext框架代码安装和启动(一)

    兄弟们,人生需要指引,而复制是成功最快的方式,让我们开始行动吧 --codesoft 教程介绍 ABP-Vnext框架我们之前摸了无数次,好象初恋的女孩,一直在靠近,一直在努力,一直不敢盯着她的眼睛说 ...

  9. 《Effective C#》系列之(零)——概要

    把全书的内容讲述完整可能需要很长时间,我可以先回答主要目录和核心的内容.如果您有任何特定问题或需要更详细的解释,请告诉我. <Effective C#>一书共包含50条C#编程建议,以下是 ...

  10. 对于小程序canvas在某些情况下touchmove 不能触发导致的签名不连续替代方案(企微)

    1.问题 微信开放社区链接 尝试过新版canvas,在企业微信中签名依然是依然断触,有问题的手机是iphoe15,系统版本以及企微版本微信版本均与签名正常的手机一致,但是那个手机就是无法正常签字,在微 ...