在这个例子中,给定一个执行遍历操作的图类,mixin提供了 * 计算顶点数 * 的功能。 通常,在C中,mixin是通过CRTP实现的,这个线程可以很好地阅读C中的mixin实现:What is C++ Mixin-Style? 下面是一个利用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>
{
};
为了理解这个概念,暂时忘记课堂,想想(最流行的)JavaScript。其中对象是方法和属性的动态数组。可以通过它们的名称作为符号或字符串文字来调用。在2018年,你将如何在标准C中实现它?* 不容易 *。但这是概念的核心。在JavaScript中,你可以添加和删除(又名混合)无论何时何地,非常重要:没有类继承。 现在到C。标准的C有你需要的一切,在这里作为一个语句没有帮助。显然,我不会写一个脚本语言,以实现混合使用C。 是的,this is a good article,但只是为了灵感。CRTP不是万能的。而且所谓的学术方法也是here,(本质上)也是基于CRTP的。 在否决这个答案之前,也许可以考虑一下我的p.o.c. code on wand box:)
struct C {
using const_iterator = /* C specific type */;
const_iterator begin() const {
return /* C specific implementation */;
}
const_iterator cbegin() const {
return begin(); // same code in every iterable class
}
};
struct B {
// ???: No information about C!
??? cbegin() const {
return ???.begin();
}
};
struct C: B {
using const_iterator = /* C specific type */;
const_iterator begin() const {
return /* C specific implementation */;
}
};
struct B {
// 1. Compiler can deduce return type from implementation
// 2. Compiler can deduce derived objects type by explicit this
decltype(auto) cbegin(this auto const& self) {
return self.begin();
}
};
struct C: B {
using const_iterator = /* C specific type */;
const_iterator begin() const {
return /* C specific implementation */;
}
};
// This was CRTP used until C++20!
template <typename T>
struct B {
// Compiler can deduce return type from implementation
decltype(auto) cbegin() const {
// We trust that T is the actual class of the current object
return static_cast<T const&>(*this).begin();
}
};
struct C: B<C> {
using const_iterator = /* C specific type */;
const_iterator begin() const {
return /* C specific implementation */;
}
};
CRTP复杂且容易出错 *
此外,这里可能很快就会出现严重的打字错误,我将稍微修改一下这个示例,使错误的后果更加明显。
#include <iostream>
struct C;
struct D;
template <typename T>
struct B {
decltype(auto) cget() const {
return static_cast<T const&>(*this).get();
}
};
struct C: B<C> {
short port = 80;
short get() const {
return port;
}
};
// Copy & Paste BUG: should be `struct D: B<**D**>`
struct D: B<C> {
float pi = 3.14159265359f;
float get() const {
return pi;
}
};
int main () {
D d;
// compiles fine, but calles C::get which interprets D::pi as short
std::cout << "Value: " << d.cget() << '\n';
// prints 'Value: 4059' on my computer
}
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.
7条答案
按热度按时间o8x7eapl1#
在讨论什么是混合之前描述它试图解决的问题是有用的。假设你有一堆想法或概念要建模。它们可能在某种程度上相关,但在大多数情况下是正交的--这意味着它们可以彼此独立。现在你可以通过继承来建模,并从某个公共接口类中派生出这些概念,然后在实现该接口的派生类中提供具体的方法。
这种方法的问题在于,这种设计没有提供任何清晰直观的方法来获取这些具体类中的每一个并将它们组合在一起。
mix-in的思想是提供一组原语类,其中每个类都建模一个基本的正交概念,并且能够把它们粘在一起组成更复杂的类,这些类具有你想要的功能--有点像乐高积木。这是可扩展的,因为以后您可以向集合中添加其他原语类,而不会影响现有的原语类。
回到C++,实现这一点的技术是使用模板和继承。这里的基本思想是通过template参数提供这些构建块,将它们连接在一起。然后将它们链接在一起,例如通过
typedef
,形成包含所需功能的新类型。以你的例子为例,假设我们想在顶部添加一个重做功能,它可能是这样的:
你会注意到我做了一些改变从你的原始:
value_type
,以使其使用起来不那么麻烦,这样你就不必每次粘贴一个模板时都输入<foobar, int>
。typedef
。注意,这只是一个简单的例子来说明混合的思想,所以它没有考虑极端情况和有趣的用法。例如,执行
undo
而不设置一个数字可能不会像你所期望的那样。作为旁注,您可能会发现this article也很有用。
csga3l582#
我喜欢greatwolf的回答,但也要提出一点警告。
greatwolf说:“这里真的不需要虚函数,因为我们在编译时就确切地知道我们的组合类类型是什么。”不幸的是,如果以多态方式使用对象,可能会遇到一些不一致的行为。
让我从他的例子中调整main函数:
通过使“set”函数成为虚拟函数,将调用适当的覆盖,并且不会发生上述不一致的行为。
mo49yndu3#
mixin是为另一个类提供功能而设计的类,通常通过指定的类提供功能所需的基本特性。例如,考虑以下示例:
本例中的mixin提供撤销值类的set操作的功能,该功能基于参数化类(在本例中为
Number
类)提供的get/set
功能。另一个示例(摘自"Mixin-based programming in C++"):
在这个例子中,给定一个执行遍历操作的图类,mixin提供了 * 计算顶点数 * 的功能。
通常,在C中,mixin是通过CRTP实现的,这个线程可以很好地阅读C中的mixin实现:What is C++ Mixin-Style?
下面是一个利用CRTP习惯用法的mixin示例(多亏了@Simple):
这个mixin提供了对一组(层次结构)形状类的 * 异构复制 * 功能。
e1xvtsh34#
C++中的mixin是使用Curiously Recurring Template Pattern(CRTP)表示的。This post是对其他重用技术(编译时多态性)所提供的功能的一个很好的细分。
cld4siwp5#
为了理解这个概念,暂时忘记课堂,想想(最流行的)JavaScript。其中对象是方法和属性的动态数组。可以通过它们的名称作为符号或字符串文字来调用。在2018年,你将如何在标准C中实现它?* 不容易 *。但这是概念的核心。在JavaScript中,你可以添加和删除(又名混合)无论何时何地,非常重要:没有类继承。
现在到C。标准的C有你需要的一切,在这里作为一个语句没有帮助。显然,我不会写一个脚本语言,以实现混合使用C。
是的,this is a good article,但只是为了灵感。CRTP不是万能的。而且所谓的学术方法也是here,(本质上)也是基于CRTP的。
在否决这个答案之前,也许可以考虑一下我的p.o.c. code on wand box:)
ijnw1ujt6#
在C20以前,CRTP是C中实现mixin的标准解决方案。mixin用于避免代码重复。它是一种编译时多态。
一个典型的例子是迭代器支持接口,许多函数的实现完全相同,例如
C::const_iterator C::cbegin() const
总是调用C::const_iterator C::begin() const
。**注意:**在C++中,
struct
与class
相同,只是成员和继承在默认情况下是公共的。C++还没有提供对这种默认实现的直接支持,但是,当
cbegin()
被移动到基类B
时,它没有关于派生类C
的类型信息。从C++23开始:显式
this
编译器在编译时就知道对象的具体数据类型,在C20之前根本没有办法得到这个信息。从C23开始你可以使用explicit this(P0847)来得到它。
这种类型的mixin易于实现,易于理解,易于使用,并且对错别字非常健壮!它在各个方面都上级经典的CRTP。
C++20之前的历史解决方案:培训中心
使用CRTP时,您将派生类的数据类型作为模板参数传递给基类。因此,基本上相同的实现是可能的,但语法要难理解得多。
此外,这里可能很快就会出现严重的打字错误,我将稍微修改一下这个示例,使错误的后果更加明显。
这是一个非常危险的错误,因为编译器无法检测到它!
prdp8dxp7#
这与接口的工作原理相同,也许更像是一个抽象,但接口更容易在第一次获得。
它解决了许多问题,但我发现在开发中经常出现的一个问题是外部API。
你有一个用户数据库,这个数据库有一定的方式来访问它的数据。现在想象一下你有一个Facebook,它也有一定的方式来访问它的数据(API)。
在任何时候,你的应用程序都可能需要使用Facebook或你的数据库中的数据运行。所以你要做的是创建一个接口,上面写着“任何实现我的东西都肯定会有以下方法”,现在你可以在你的应用程序中实现这个接口了...
因为接口承诺实现存储库将在其中声明方法,所以您知道无论何时何地在应用程序中使用该接口,如果切换数据,它总是会有您定义的方法,因此有数据可供使用。
这种工作模式还有很多层,但本质是它很好,因为数据或其他此类持久性项目成为应用程序的重要组成部分,如果它们在您不知情的情况下发生更改,您的应用程序可能会中断:)
这是一些伪代码。