C++ 什么是 Mixin(作为一个概念)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18773367/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 22:12:08  来源:igfitidea点击:

What are Mixins (as a concept)

c++ooptemplatesmixins

提问by Shookie

I'm trying to get my head around the Mixin concept but I can't seem to understand what it is. The way I see it is that it's a way to expand the capabilities of a class by using inheritance. I've read that people refer to them as "abstract subclasses". Can anyone explain why?

我试图理解 Mixin 的概念,但我似乎无法理解它是什么。我认为它是一种通过使用继承来扩展类功能的方法。我读过人们将它们称为“抽象子类”。谁能解释为什么?

I'd appreciate if you'd explain your answer based on the following example (From one of my lecture slideshows): A C++ Mixin Example

如果您能根据以下示例(来自我的演讲幻灯片之一)解释您的答案,我将不胜感激: C++ Mixin 示例

回答by greatwolf

Before going into what a mix-in is, it's useful to describe the problems it's trying to solve. Say you have a bunch of ideas or concepts you are trying to model. They may be related in some way but they are orthogonal for the most part -- meaning they can stand by themselves independently of each other. Now you might model this through inheritance and have each of those concepts derive from some common interface class. Then you provide concrete methods in the derived class that implements that interface.

在讨论混合是什么之前,描述它试图解决的问题很有用。假设您有一堆要建模的想法或概念。它们可能以某种方式相关,但它们在大多数情况下是正交的——这意味着它们可以相互独立。现在,您可以通过继承对此进行建模,并让这些概念中的每一个都派生自某个通用接口类。然后在实现该接口的派生类中提供具体方法。

The problem with this approach is that this design does not offer any clear intuitive way to take each of those concrete classes and combine them together.

这种方法的问题在于,这种设计没有提供任何清晰直观的方法来获取每个具体类并将它们组合在一起。

The idea with mix-ins is to provide a bunch of primitive classes, where each of them models a basic orthogonal concept, and be able to stick them together to compose more complex classes with just the functionality you want -- sort of like legos. The primitive classes themselves are meant to be used as building blocks. This is extensible since later on you can add other primitive classes to the collection without affecting the existing ones.

mix-ins 的想法是提供一堆原始类,其中每个类都模拟一个基本的正交概念,并且能够将它们粘在一起以组成更复杂的类,仅具有您想要的功能——有点像乐高积木。原始类本身旨在用作构建块。这是可扩展的,因为稍后您可以将其他原始类添加到集合中,而不会影响现有的类。

Getting back to C++, a technique for doing this is using templates and inheritance. The basic idea here is you connect these building blocks together by providing them via the template parameter. You then chain them together, eg. via typedef, to form a new type containing the functionality you want.

回到 C++,这样做的一种技术是使用模板和继承。这里的基本思想是通过模板参数将这些构建块连接在一起。然后你将它们链接在一起,例如。通过typedef, 形成一个包含你想要的功能的新类型。

Taking your example, let say we want to add a redo functionality on top. Here's how it might look like:

以您的示例为例,假设我们要在顶部添加重做功能。下面是它的样子:

#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';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // back to 84
}

You'll notice I made a few changes from your original:

你会注意到我对你的原作做了一些改动:

  • The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time.
  • I've added a default value_typefor the second template param to make its usage less cumbersome. This way you don't have to keep typing <foobar, int>everytime you stick a piece together.
  • Instead of creating a new class that inherits from the pieces, a simple typedefis used.
  • 虚函数在这里真的不是必需的,因为我们在编译时确切地知道我们组合的类类型是什么。
  • value_type为第二个模板参数添加了一个默认值,以使其使用不那么麻烦。这样您就不必<foobar, int>每次将一块拼贴在一起时都继续打字。
  • 不是创建一个从这些部分继承的新类,而是typedef使用一个简单的。

Note that this is meant to be a simple example to illustrate the mix-in idea. So it doesn't take into account corner cases and funny usages. For example, performing an undowithout ever setting a number probably won't behave as you might expect.

请注意,这只是一个简单的例子来说明混入的想法。所以它没有考虑极端情况和有趣的用法。例如,在undo不设置数字的情况下执行操作可能不会像您预期的那样运行。

As a sidenote, you might also find this articlehelpful.

作为旁注,您可能还会发现这篇文章很有帮助。

回答by Manu343726

A mixin is a class dessigned to provide functionality for another class, normally through a specified class which provides the basic features that the functionality needs. For example, consider your example:
The mixin in this case provides the functionality of undoing the set operation of a value class. This hability is based on the get/setfunctionality provided by a parametrized class (The Numberclass, in your example).

mixin 是一个类,旨在为另一个类提供功能,通常通过指定的类提供功能所需的基本特性。例如,考虑您的示例:
本例中的 mixin 提供了撤消值类的 set 操作的功能。此get/set功能基于参数化类(Number在您的示例中为类)提供的功能。

Another example (Extracted from "Mixin-based programming in C++"):

另一个例子(摘自基于 Mixin 的 C++ 编程):

template <class Graph>
class Counting: public Graph {
  int nodes_visited, edges_visited;
public:
  Counting() : nodes_visited(0), edges_visited(0), Graph() { }
  node succ_node (node v) {
    nodes_visited++;
    return Graph::succ_node(v);
  }
  edge succ_edge (edge e) {
    edges_visited++;
    return Graph::succ_edge(e);
  }
... 
};

In this example, the mixin provides the functionality of counting vertices, given a graph class that performs trasversal operations.

在这个例子中,mixin 提供了计算顶点的功能,给定一个执行遍历操作的图类。

Commonly, in C++ mixins are implemented through the CRTPidiom. This thread could be a good read about a mixin implementation in C++: What is C++ Mixin-Style?

通常,在 C++ 中,mixin 是通过CRTP习惯用法实现的。这个线程可以很好地了解 C++ 中的 mixin 实现:什么是 C++ Mixin-Style?

Here is an example of a mixin that takes advantage of the CRTP idiom (Thanks to @Simple):

这是一个利用 CRTP 习语的 mixin 示例(感谢@Simple):

#include <cassert>
#ifndef NDEBUG
#include <typeinfo>
#endif

class shape
{
public:
    shape* clone() const
    {
        shape* const p = do_clone();
        assert(p && "do_clone must not return a null pointer");
        assert(
            typeid(*p) == typeid(*this)
            && "do_clone must return a pointer to an object of the same type"
        );
        return p;
    }

private:
    virtual shape* do_clone() const = 0;
};

template<class D>
class cloneable_shape : public shape
{
private:
    virtual shape* do_clone() const
    {
        return new D(static_cast<D&>(*this));
    }
};

class triangle : public cloneable_shape<triangle>
{
};

class square : public cloneable_shape<square>
{
};

This mixin provides the functionality of heterogeneous copyto a set (hierarchy) of shape classes.

这个mixin 提供了异构复制到一组(层次结构)形状类的功能。

回答by Ken

I like the answer from greatwolf, but would offer one point of caution.

我喜欢 Greatwolf 的回答,但会提出一点警告。

greatwolf stated, "The virtual functions really aren't necessary here because we know exactly what our composed class type is at compile-time." Unfortunately, you can run into some inconsistent behavior if you use your object polymorphically.

Greatwolf 说:“这里真的不需要虚函数,因为我们在编译时就知道我们组合的类是什么类型。” 不幸的是,如果您多态地使用您的对象,您可能会遇到一些不一致的行为。

Let me tweak the main function from his example:

让我从他的例子中调整 main 函数:

int main()
{
  ReUndoableNumber mynum;
  Undoable<Number>* myUndoableNumPtr = &mynum;

  mynum.set(42);                // Uses ReUndoableNumber::set
  myUndoableNumPtr->set(84);    // Uses Undoable<Number>::set (ReUndoableNumber::after not set!)
  cout << mynum.get() << '\n';  // 84
  mynum.undo();
  cout << mynum.get() << '\n';  // 42
  mynum.redo();
  cout << mynum.get() << '\n';  // OOPS! Still 42!
}  

By making the "set" function virtual, the proper override will be called and the inconsistent behavior above will not occur.

通过将“set”函数设为虚拟,将调用适当的覆盖并且不会发生上述不一致的行为。

回答by Jesse Pepper

Mixins in C++ are expressed using the Curiously Recurring Template Pattern(CRTP). This postis an excellent breakdown of what they provide over other reuse techniques... compile-time polymorphism.

C++ 中的 Mixin 使用Curiously Recurring Template Pattern(CRTP) 表示。 这篇文章很好地分解了他们提供的其他重用技术......编译时多态性。

回答by Jesse Pepper

To understand the concept forget classes for a moment. Think (most popular) JavaScript. Where objects are dynamic arrays of methods and properties. Callable by their name as a symbol or as a string literal. How would you implement that in standard C++ in a year 2018? Not easily. But that is the core of the concept. In JavaScript one can add and remove (aka mix-in) whenever and whatever one wishes to. Very important: No class inheritance.

为了理解这个概念,暂时忘记类。想想(最流行的)JavaScript。其中对象是方法和属性的动态数组。可通过它们的名称作为符号或字符串文字调用。在 2018 年,您将如何在标准 C++ 中实现它?不容易。但这是概念的核心。在 JavaScript 中,人们可以随时随地添加和删除(也就是混入)。非常重要:没有类继承。

Now onto C++. Standard C++ has all you need, does not help as a statement here. Obviously I will not write a scripting language in order to implement mix-in using C++.

现在到 C++。标准 C++ 拥有您所需要的一切,在这里作为声明无济于事。显然,我不会编写脚本语言来使用 C++ 实现混合。

Yes, this is a good article, but for inspiration only. CRTP is not a panacea. And also the so called academic approach is here, also (in essence) CRTP based.

是的,这是一篇好文章,但仅供参考。CRTP 不是灵丹妙药。所谓的学术方法也在这里,也(本质上)基于CRTP。

Before down-voting this answer perhaps consider my p.o.c. code on wand box:)

在否决此答案之前,请考虑我在魔杖盒上的 poc 代码:)

回答by Jimmyt1988

This works the same as an interface and maybe more so as an abstract, but interfaces are easier to get first time.

这与接口的工作方式相同,并且作为抽象可能更是如此,但接口更容易第一次获得。

It addresses many issues but one I find in development that comes up a lot is external apis. imagine this.

它解决了许多问题,但我发现在开发中经常出现的一个问题是外部 api。想象一下。

You have a database of users, that database has a certain way of getting access to its data. now imagine you have facebook, that also has a certain way of getting access to its data (api).

您有一个用户数据库,该数据库有某种方式可以访问其数据。现在想象一下你有 facebook,它也有某种方式来访问它的数据 (api)。

at any point your application may need to run using data from facebook or your database. so what you do is create an interface that says "anything that implements me is going to definitely have the following methods" now you can implement that interface into your application...

在任何时候,您的应用程序都可能需要使用来自 facebook 或您的数据库的数据来运行。所以你要做的是创建一个接口,上面写着“任何实现我的东西肯定会有以下方法”,现在你可以将该接口实现到你的应用程序中......

because an interface promises that the implementing repositories will have the methods declared in them, you know that wherever or whenever you use that interface in your application, if you switch the data over, it's always going to have the methods you are defining and thus have data to work off of.

因为接口承诺实现存储库将在其中声明方法,所以您知道无论何时何地在应用程序中使用该接口,如果您切换数据,它将始终具有您定义的方法,因此具有要工作的数据。

There are many more layers to this pattern of working, but the essence is that it is good because data or other such persistant items become a big part of your application, and if they change without you knowing, your application can break :)

这种工作模式有更多层,但本质是它很好,因为数据或其他此类持久性项目成为您应用程序的重要组成部分,如果它们在您不知情的情况下发生变化,您的应用程序可能会崩溃:)

Here's some pseudo code.

这是一些伪代码。

interface IUserRepository
{
    User GetUser();
}

class DatabaseUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for database
    }
}

class FacebookUserRepository : IUserRepository
{
    public User GetUser()
    {
        // Implement code for facebook
    }
}

class MyApplication
{
    private User user;

    MyApplication( IUserRepository repo )
    {
        user = repo;
    }
}

// your application can now trust that user declared in private scope to your application, will have access to a GetUser method, because if it isn't the interface will flag an error.