问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C++中的Mixin惯用法详解

创作时间:
作者:
@小白创作中心

C++中的Mixin惯用法详解

引用
CSDN
1.
https://blog.csdn.net/guangcheng0312q/article/details/138885341

Mixin是C++中一种非常实用的编程技巧,它允许开发者将一些小功能片段组合到类中,从而实现类似于多重继承的效果。本文将详细介绍Mixin的概念、实现方法以及具体应用场景,帮助读者掌握这一强大的编程工具。

C++中的Mixin惯用法

混合(Mixins)是Lisp中的一个概念。混合是类的一部分,意味着它旨在与其他类或混合组合在一起。常规独立类(例如Person)与混合的区别在于,混合模拟了一些小功能片段(例如打印或显示),并不用于独立使用。相反,它应该与需要此功能的其他类(例如Person)组合在一起。

因此,混合的目的是允许类似于多重继承的东西。

在C++汇总mixin的基本写法如下:

template <class Super>
class Mixin : public Super {
  /* mixin body */
};

或者

template<typename... Super>
class Mixin : public Super... {
 public:
  Mixin() : Super...() {}
  // ...
};

示例1:缩放与旋转

假设我们要对一个长方形/正方形进行缩放、旋转、添加边框等,这些操作都会影响其宽度与高度,我们可以使用mixin来实现。首先,我们可以定义好正常形与长方形作为mixin的基类。

// 正方形类
class Square : public Shape {
public:
  Square(int sideLength) : sideLength(sideLength) {}
  int GetWidth() const override { return sideLength; }
  int GetHeight() const override { return sideLength; }
private:
  int sideLength;
};

// 长方形类
class Rectangle : public Shape {
public:
  Rectangle(int width, int height) : width(width), height(height) {}
  int GetWidth() const override { return width; }
  int GetHeight() const override { return height; }
private:
  int width;
  int height;
};

随后使用mixin子类去拓展功能,例如:一个缩放的mixin我们可以自定义一个类,它的宽度与高度等于scale乘以对应的宽高。

// 缩放的Mixin
template <class Base, int ScaleX, int ScaleY>
class Scale : public Base {
 public:
  int GetScaledWidth() const { return Base::GetWidth() * ScaleX; }
  int GetScaledHeight() const { return Base::GetHeight() * ScaleY; }
};

像这种场景非常适合使用mixin。使用的时候:

using ModifiedShape = Border<RotateBy90<Scale<Square, 2, 1>>, 10>;

示例2:redo与undo

完整示例模拟一个redo与undo操作,我们便可以在数据之间来回的切换。Redoable<Undoable>,在这里我们可以将这些类混合到一起使用了。

#include <iostream>
using namespace std;

struct Number {
  typedef int value_type;
  int n;
  void set(int v) {
    n = v;
  }
  int get() const {
    return n;
  }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Undoable : public BASE {
  typedef T value_type;
  T before;
  void set(T v) {
    before = BASE::get();
    BASE::set(v);
  }
  void undo() {
    BASE::set(before);
  }
};

template <typename BASE, typename T = typename BASE::value_type>
struct Redoable : public BASE {
  typedef T value_type;
  T after;
  void set(T v) {
    after = v;
    BASE::set(v);
  }
  void redo() {
    BASE::set(after);
  }
};

typedef Redoable<Undoable<Number>> ReUndoableNumber;

int main() {
  ReUndoableNumber mynum;
  mynum.set(42);
  mynum.set(84);
  cout << mynum.get() << '\n';  // Output: 84
  mynum.undo();
  cout << mynum.get() << '\n';  // Output: 42
  mynum.redo();
  cout << mynum.get() << '\n';  // Output: back to 84
}

示例3:重复打印

再举一个拼积木的例子:重复打印。首先,有一个类,我们可以print这个人的名字。

class Name {
public:
  Name(std::string firstName, std::string lastName)
    : firstName_(std::move(firstName))
    , lastName_(std::move(lastName)) {}

  void print() const {
    std::cout << lastName_ << ", " << firstName_ << '\n';
  }

private:
  std::string firstName_;
  std::string lastName_;
};

那如果我想重复打印呢?我们可以这样玩,使用mixin,将Name类传递进去。

template<typename Printable>
struct RepeatPrint : Printable {
  explicit RepeatPrint(Printable const& printable) : Printable(printable) {}
  void repeat(unsigned int n) const {
    while (n-- > 0) {
      this->print();
    }
  }
};

template<typename Printable>
RepeatPrint<Printable> repeatPrint(Printable const& printable) {
  return RepeatPrint<Printable>(printable);
}

使用:

Name ned("Eddard", "Stark");
repeatPrint(ned).repeat(10);

此时,便得到重复n次的打印结果。

那么mixin比较适合什么场景呢?

  • 保持原来的类不变,
  • 客户端代码不直接使用原始类,它需要将其包装到 mixin 中才能使用增强功能,

标准库

在标准库当中有一个使用mixin技术:std::nested_exception。

std::nested_exception 是一个多态 mixin 类,它可以捕获并存储当前异常,从而可以在彼此之间嵌套任意类型的异常。

template <typename _Except>
struct _Nested_exception : public _Except, public nested_exception
{}

在源码中我们可以看到使用了nested_exception作为mixin的基类。

参考


© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号