设计模式-基本概念

news2025/2/24 6:26:33

设计模式-基本概念

  • 基本概念
    • 奇异递归模板模式(CRTP)
      • 说明
      • 示例
        • 例子1:对象计数
        • 例子2:多态复制构造
        • 例子4:std::enable_shared_from_this
        • 例子5 树简单遍历
    • 混合继承
    • 属性
    • SOLID 设计原则
  • 参考

基本概念

奇异递归模板模式(CRTP)

奇异递归模板模式(curiously recurring template pattern,CRTP)是C++模板编程时的一种惯用法(idiom):把派生类作为基类的模板参数。

说明

静态多态
C++语言的多态,原本是用虚函数来实现的,属于动态多态。安德烈在《Modern C++ Design》中提出了奇异递归模板模式,并称之为静态多态(static polymorphism)。

#include <iostream>
using namespace std;

template <typename Child>
struct Base {
    void interface() { static_cast<Child*>(this)->implementation(); }
    static void static_func() { Child::static_sub_func(); }
};

struct Derived : Base<Derived> {
    void implementation() { cout << "Derived implementation\n"; }
    static void static_sub_func() { cout << "child function " << endl; }
};

int main() {
    Derived d;
    d.interface();  // Prints "Derived implementation"
    Base<Derived> m;
    m.static_func();
}

运行结果

Derived implementation
child function 

基类模板利用了其成员函数体(即成员函数的实现)在声明之后很久都不会被实例化(实际上只有被调用的模板类的成员函数才会被实例化),并利用了派生类的成员函数(通过类型转化)。
在上例中,Base<Derived>::interface(),虽然是在struct Derived之前就被声明了,但未被编译器实例化直至它被实际调用,这发生于Derived声明之后,此时Derived::implementation()的声明是已知的。
这种技术获得了类似于虚函数的效果,并避免了动态多态的代价。也有人把CRTP称为“模拟的动态绑定”。
这种模式广泛用于Windows ATLWTL库,以及Boost.IteratorBoost.Python或者Boost.Serialization等库中。
考虑一个基类,没有虚函数,则它的成员函数能够调用的其它成员函数,只能是属于该基类自身。当从这个基类派生其它类时,派生类继承了所有未被覆盖(overridden)的基类的数据成员与成员函数 。如果派生类调用了一个被继承的基类的函数**,而该函数又调用了其它成员函数,这些成员函数不可能是派生类中的派生或者覆盖的成员函数。也就是说,基类中是看不到派生类的。但是,基类如果使用了CRTP,则在编译时派生类的覆盖的函数可被选中调用。这效果相当于编译时模拟了虚函数调用但避免了虚函数的尺寸与调用开销(VTBL结构与方法查找、多继承机制)等代价。但CRTP缺点是不能在运行时做出动态绑定。

不通过虚函数机制,基类访问派生类的私有或保护成员,需要把基类声明为派生类的友元friend)。如果一个类有多个基类都出现这种需求,声明多个基类都是友元会很麻烦。一种解决技巧是在派生类之上再派生一个accessor类,显然accessor类有权访问派生类的保护函数;如果基类有权访问accessor类,就可以间接调用派生类的保护成员了。这种方法被boost的多个库使用,如:Boost.Python中的def_visitor_accessBoost.Iterator的iterator_core_access。原理示例代码如下:

template<class DerivedT> class Base {
  private:
    struct accessor : DerivedT { // accessor类没有数据成员,只有一些静态成员函数
        static int foo(DerivedT& derived) {
            int (DerivedT::*fn)() = &DeriveT::do_foo; //获取DerivedT::do_foo的成员函数指针  
            return (derived.*fn)();        // 通过成员函数指针的函数调用
        }
    };                                     // accessor类仅是Base类的成员类型,而没有实例化为Base类的数据成员。
  public:
    DerivedT& derived() {// 该成员函数返回派生类的实例的引用
       return static_cast<DerivedT&>(*this);
    }
    int foo() {//  该函数具体实现了业务功能
        return accessor::foo( this->derived());
    }
};
 
struct Derived : Base<Derived> { //  派生类不需要任何特别的友元声明
  protected: 
    int do_foo() {
         // ... 具体实现 
         return 1; 
     }
};

示例

例子1:对象计数

统计一个类的实例对象创建与析构的数据。可以轻松地利用CRTP实现:

template <typename T>
struct counter
{
    static int objects_created;
    static int objects_alive;

    counter()
    {
        ++objects_created;
        ++objects_alive;
    }
    
    counter(const counter&)
    {
        ++objects_created;
        ++objects_alive;
    }
protected:
    ~counter() // objects should never be removed through pointers of this type
    {
        --objects_alive;
    }
};
template <typename T> int counter<T>::objects_created( 0 );
template <typename T> int counter<T>::objects_alive( 0 );

class X : counter<X>
{
    // ...
};

class Y : counter<Y>
{
    // ...
};

例子2:多态复制构造

当使用多态时,常需要基于基类指针创建对象的一份拷贝。常见办法是增加clone虚函数在每一个派生类中。使用CRTP,可以避免在派生类中增加这样的虚函数。

// Base class has a pure virtual function for cloning
class Shape {
public:
    virtual ~Shape() {}
    virtual Shape *clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape {
public:
    virtual Shape *clone() const {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP<Type>

// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

This allows obtaining copies of squares, circles or any other shapes by shapePtr->clone().
例子3:不可派生的类

一个类如果不希望被继承,类似于Java中的具有finally性质的类,这在C++中可以用虚继承来实现:

template<typename T> class MakeFinally{
   private:
       MakeFinally(){}//只有MakeFinally的友类才可以构造MakeFinally
       ~MakeFinally(){}
   friend T;
};

class MyClass:public virtual  MakeFinally<MyClass>{};//MyClass是不可派生类

//由于虚继承,所以D要直接负责构造MakeFinally类,从而导致编译报错,所以D作为派生类是不合法的。
class D: public MyClass{};
//另外,如果D类没有实例化对象,即没有被使用,实际上D类是被编译器忽略掉而不报错

int main()
{
MyClass var1;
// D var2;  //这一行编译将导致错误,因为D类的默认构造函数不合法
}

例子4:std::enable_shared_from_this

在C++标准库头文件中,std::shared_ptr类封装了可被共享使用的指针或资源。一个被共享的对象不能直接把自身的原始指针(raw pointer)this传递给std::shared_ptr的容器对象(如一个std::vector),因为这会生成该被共享的对象的额外的共享指针控制块。为此,std::shared_ptr API提供了一种类模板设施std::enable_shared_from_this,包含了成员函数shared_from_this,从而允许从this创建一个std::shared_ptr对象。

class mySharedClass:public  std::enable_shared_from_this<mySharedClass>{
public:
  // ...
};

int main()
{
  std::vector<std::shared_ptr<mySharedClass>> spv;
  spv.push_back(new mySharedClass());
  std::shared_ptr<mySharedClass> p(new mySharedClass());
  mySharedClass &c=*p;
  spv.emplace_back(c.shared_from_this());
}

继承者将自己作为模板参数传递给它的基类。原因之一是,以便于能够访问基类实现中的类型化 this 指针。

struct Foo : SomeBase<Foo>
{
	...
}

例如,假设 SomeBase 的每个继承者都实现了迭代所需的 begin()/end() 对。那么,您将如何在 SomeBase 的成员中迭代该对象?直觉表明,您不能这样做,因为 SomeBase 本身没有提供 begin()/end() 接口。但是,如果您使用 CRTP,实际上是可以将 this 转换为派生类类型:

template <typename Derived>
struct SomeBase
{
    void foo()
    {
        for (auto& item : *static_cast<Derived*>(this))
        {
            ...
        }
    }
}

例子5 树简单遍历

#include <assert.h>

#include <iostream>
using namespace std;

struct TreeNode {
    enum Kind { RED, BLUE, YELLOW };

    TreeNode(Kind kind_, TreeNode* left_ = NULL, TreeNode* right_ = NULL)
        : kind(kind_), left(left_), right(right_) {}

    Kind kind;
    TreeNode *left, *right;
};

template <typename Derived>
class GenericVisitor {
   public:
    void visit_preorder(TreeNode* node) {
        if (node) {
            dispatch_node(node);
            visit_preorder(node->left);
            visit_preorder(node->right);
        }
    }

    void visit_inorder(TreeNode* node) {
        if (node) {
            visit_inorder(node->left);
            dispatch_node(node);
            visit_inorder(node->right);
        }
    }

    void visit_postorder(TreeNode* node) {
        if (node) {
            visit_postorder(node->left);
            visit_postorder(node->right);
            dispatch_node(node);
        }
    }

    void handle_RED(TreeNode* node) { cout << "Generic handle RED\n"; }

    void handle_BLUE(TreeNode* node) { cout << "Generic handle BLUE\n"; }

    void handle_YELLOW(TreeNode* node) { cout << "Generic handle YELLOW\n"; }

   private:
    // Convenience method for CRTP
    Derived& derived() { return *static_cast<Derived*>(this); }

    void dispatch_node(TreeNode* node) {
        switch (node->kind) {
            case TreeNode::RED:
                derived().handle_RED(node);
                break;
            case TreeNode::BLUE:
                derived().handle_BLUE(node);
                break;
            case TreeNode::YELLOW:
                derived().handle_YELLOW(node);
                break;
            default:
                assert(0);
        }
    }
};

class SpecialVisitor : public GenericVisitor<SpecialVisitor> {
   public:
    void handle_RED(TreeNode* node) { cout << "RED is special\n"; }
};

int main() {
    TreeNode node(TreeNode::RED);
    node.left = new TreeNode(TreeNode::BLUE);
    node.right = new TreeNode(TreeNode::YELLOW);

    SpecialVisitor d;
    // d.handle_RED(&node);  // Prints "Derived implementation"
    // d.handle_BLUE(&node);
    cout << "visit_postorder " << endl;
    d.visit_postorder(&node);
    cout << "\nvisit_preorder " << endl;
    d.visit_preorder(&node);
    cout << "\nvisit_inorder\n";
    d.visit_inorder(&node);
    delete (node.left);
    delete (node.right);
}

运行结果:

visit_postorder 
Generic handle BLUE
Generic handle YELLOW
RED is special

visit_preorder 
RED is special
Generic handle BLUE
Generic handle YELLOW

visit_inorder
Generic handle BLUE
RED is special
Generic handle YELLOW

混合继承

在 C++ 中,类可以定义为继承自它自己的模板参数,例如:

template <typename T> struct Mixin : T
{
	...
}

这种方法被称为混合继承(mixin inheritance),并允许类型的分层组合。例如,您可以允许Foo\<Bar\<Baz>> x;声明一个实现所有三个类的特征的类型的变量,而不必实际构造一个全新的 FooBarBaz 类型。

属性

一个属性(property,通常是私有的)仅仅是字段以及 gettersetter 的组合。在标准 C++ 中,一个属性如下所示:

class Person
{
    int age;
public:
    int get_age() const { return age; }
    void set_age(int value) { age = value; }
};

大量的编程语言(例如,C#Kotlin)通过直接将其添加到编程语言中,将属性的概念内化。虽然 C++ 没有这样做(而且将来也不太可能这样做),但是有一个名为 property 的非标准声明说明符,您可以在大多数编译器(MSVC、Clang、Intel)中使用:

class Person
{
    int age_;
public:
    int get_age() const { return age_; }
    void set_age(int value) { age_ = value; }
    __declspec(property(get=get_age, put=set_age)) int age;
};

这可以按如下所示使用:

Person person;
p.age = 20; // calls p.set_age(20)

在这里插入图片描述

SOLID 设计原则

SOLID 是一个首字母缩写,代表以下设计原则(及其缩写):

  • 单一责任原则(SRP)
  • 开闭原则(OCP)
  • 里氏替换原则(LSP)
  • 接口隔离原则(ISP)
  • 依赖注入原则(DIP)

详细请翻阅设计模式之基本原则

参考

[1] CRTP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/491317.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

带你彻底理解Spark的分区

前言 我&#xff1a;什么是RDD&#xff1f; 面试者&#xff1a;RDD是被分区的&#xff0c;由一系列分区组成… … 我&#xff1a;你怎么理解分区&#xff1f; 面试者&#xff1a;… 我&#xff1a;Spark中有哪些可以实现分区的方法&#xff1f;分别使用的场景是什么&#xff1…

nodejs-前端工程化环境-安装-webpack打包工具

文章目录 1.安装nodejs1.1.新建项目1.2.安装jQuery。1.3.查看全局模块安装目录 2.安装Vue2.1.安装2.2.创建vue项目 3.安装webpack4.安装 Grunt5.安装uglify-js > js代码压缩打包工具。6.因为在此系统上禁止运行脚本……解决办法 1.安装nodejs 从官网下载长期支持版本&#…

数值分析-埃尔米特插值的概念、实现与应用

目录 一、引言 二、埃尔米特插值的基本概念 2.1 埃尔米特插值的定义 2.2 埃尔米特插值的优点 三、埃尔米特插值的实现方法 3.1 基于拉格朗日插值的埃尔米特插值 2.2 基于牛顿插值的埃尔米特插值 四、埃尔米特插值的应用 4.1 基于埃尔米特插值的函数逼近 4.2 基于埃尔…

2分钟搞懂人工智能、机器学习和深度学习

不少高校的小伙伴找我聊入门人工智能该怎么起步&#xff0c;如何快速入门&#xff0c;多长时间能成长为中高级工程师&#xff08;聊下来感觉大多数学生党就是焦虑&#xff0c;毕业即失业&#xff0c;尤其现在就业环境这么差&#xff09;&#xff0c;但聊到最后&#xff0c;很多…

java遍历集合的方法

java中&#xff0c;集合的遍历是一项基本且重要的技能。我们不仅要知道集合中元素的个数&#xff0c;还要知道元素的值&#xff0c;以及它们之间的关系。 在 Java中&#xff0c;如果需要遍历集合中的某个元素&#xff0c;可以使用以下方法&#xff1a; 1.通过 return语句将集合…

工赋开发者社区 | 装备制造企业数字化转型总体框架

导读 当前&#xff0c;面对技术、市场以及供应链等多重挑战&#xff0c;在软件定义、数据驱动、数字孪生、大数据、人工智能及元宇宙等技术加持下&#xff0c;装备制造企业不断采用新工艺、新材料&#xff0c;以新模式推动产品快速创新。企业积极关注并探索数字化转型路径&…

ThingsBoard使用docker compose集群部署

1、概述 今天我将讲解官方文档说的使用docker compose集群部署ThingsBoard,这种部署方式也是目前企业中常用的形式,希望大家能够掌握,我不是直接使用官方的镜像,我是自己拉起代码,然后自己构建镜像,在传到服务器上,使用自己的镜像来部署。而且这种部署中间有个大坑,我…

雷达原理_有源干扰_间歇采样直接、重复、循环转发干扰_含MATLAB实现代码

间歇采样直接、重复、循环转发干扰 间歇采样转发干扰是在雷达脉冲周期内对雷达信号进行间歇采样&#xff0c;并通过干扰机将采样的信号进行处理和转发&#xff0c;从而生成相干的假目标信号。这种干扰方式的原理可分为直接转发、重复转发和逐次循环转发三种方式。直接转发是指…

这个档案室管理妙招,太有用了!

档案是人类文明发展到一定历史阶段的产物&#xff0c;是人类活动的真实记录&#xff0c;也是新的社会实践最可靠的凭证和依据。 借助档案&#xff0c;我们能够更好地了解过去、把握现在、预见未来&#xff0c;是一种宝贵的无形资产&#xff0c;也是一种不可再生资源。因此&…

Pandas + AI = PandasAI【Python】

Pandas AI 是一个 Python 库&#xff0c;它为流行的数据分析和操作工具 Pandas 添加了生成式AI能力。 PandasAI旨在与 Pandas 结合使用&#xff0c;而不是它的替代品。 推荐&#xff1a;用 NSDT场景设计器 快速搭建3D场景 1、安装PandasAI 使用如下命令安装pandas-ai&#xf…

linux中TF启动卡制作:磁盘分区文件同步

文章目录 前言&#xff1a;1. 连接TF卡2. 磁盘卸载载与分区2.1 磁盘卸载2.2 创建第一个分区2.3 创建第二个分区 3. 磁盘格式化4. 文件同步5. 检查与BOOT分区启动文件拷贝总结&#xff1a; 前言&#xff1a; TF卡在linux环境下配置好相关软件后&#xff0c;把配置好的系统以及软…

Neo4j图数据库的数据模型_包括节点_属性_数据_关系---Neo4j图数据库工作笔记0002

来看一下neo4j的特性 这个neo4j特点就是简单,这里用最快的速度学习 可以看到一个圈表示一个节点,然后两个节点直接可以有关系,关系可以是双向的

Python正则表达式详解,保姆式教学,0基础也能掌握正则

正则作为处理字符串的一个实用工具&#xff0c;在Python中经常会用到&#xff0c;比如爬虫爬取数据时常用正则来检索字符串等等。正则表达式已经内嵌在Python中&#xff0c;通过导入re模块就可以使用&#xff0c;作为刚学Python的新手大多数都听说”正则“这个术语。 今天来给…

学生成绩管理系统【纯控制台】(Java课设)

系统类型 纯控制台类型&#xff08;没有用到数据库&#xff09; 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Idea或eclipse 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87753365 更多系统…

Python每日一练(20230505) 课程表 Course Schedule III/IV

目录 3. 课程表 Course Schedule III 4. 课程表 Course Schedule IV &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 3. 课程表 Course Schedule III 这里有 n 门不同的在线课程&#xff…

python并发编程:什么是并发编程?python对并发编程有哪些支持?

Python并发编程是指同时执行多个任务的编程模式。Python提供了多种实现并发编程的方式&#xff0c;包括多线程、多进程、协程、异步IO等。 为什么要引入并发编程 假设以下两个场景&#xff1a; 场景一: 一个网络爬虫&#xff0c;按顺序爬取花了一个小时&#xff0c;采用并发…

距新发牌制度生效不到1个月,我们和数位香港Web3er聊了聊

出品&#xff5c;欧科云链研究院 作者&#xff5c;Jason Jiang 4月20日&#xff0c;欧洲议会通过加密资产市场法规&#xff08;MiCA&#xff09;,使欧盟成为全球首个引入全面加密法的主要司法管辖区。与此同时&#xff0c;东方世界的香港也正加速拥抱Web3变革。香港特区立法会…

【线程安全】内存可见性问题及解决方案

1. 关于内存可见性的一段代码 import java.util.Scanner; public class ThreadDemo {public static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {while (count 0) {}System.out.println("t1 线程…

双向链表及双向链表的常见操作和用js封装一个双向链表

书接上回&#xff0c;上一篇文章讲了单向链表以及用 js 封装一个单向链表&#xff0c;所以这节将介绍双向链表以及用 js 封装一个双向链表。待会我也会继续在文章后面附上视频学习链接地址&#xff0c;大家想学习的可以去看看 一、认识双向链表 首先来认识一下什么是双向链表&…

广和通发布5G智能模组SC151系列,助力AIoT应用更智能高效

2023年5月&#xff0c;广和通发布5G R16智能模组SC151系列。SC151系列基于4nm制程工艺的高通QCM4490解决方案设计&#xff0c;采用8核高性能处理器&#xff0c;为工业与商业物联网终端提供高性能处理能力。面对与日俱增的终端智能化需求&#xff0c;SC151系列将助力打造高生产力…