我应该更喜欢mixin还是函数模板来将行为添加到一组不相关的类型中
Should I prefer mixins or function templates to add behavior to a set of unrelated types?
Mixin和函数模板是向一组广泛的类型提供行为的两种不同方式,只要这些类型满足某些要求。
例如,假设我想编写一些代码,允许我将对象保存到文件中,只要该对象提供toString
成员函数(这是一个相当愚蠢的示例,但请耐心等待)。第一个解决方案是编写如下函数模板:
template <typename T>
void toFile(T const & obj, std::string const & filename)
{
std::ofstream file(filename);
file << obj.toString() << 'n';
}
...
SomeClass o1;
toFile(o1, "foo.txt");
SomeOtherType o2;
toFile(o2, "bar.txt");
另一种解决方案是使用混合蛋白,使用CRTP:
template <typename Derived>
struct ToFile
{
void toFile(std::string const & filename) const
{
Derived * that = static_cast<Derived const *>(this);
std::ofstream file(filename);
file << that->toString() << 'n';
}
};
struct SomeClass : public ToFile<SomeClass>
{
void toString() const {...}
};
...
SomeClass o1;
o.toFile("foo.txt");
SomeOtherType o2;
o2.toFile("bar.txt");
这两种方法的优缺点是什么?是否有受青睐的人,如果有,为什么?
第一种方法要灵活得多,因为它可以与任何提供任何方式转换为std::string
的类型一起工作(这可以使用traits类实现),而无需修改该类型。您的第二种方法总是需要修改类型才能添加功能。
Pro函数模板:耦合比较松散。您不需要从任何东西派生就可以获得新类中的功能;在您的示例中,您只实现了toString
方法,仅此而已。由于没有指定toString
的类型,您甚至可以使用有限形式的duck类型。
Pro mixins:严格来说,什么都没有;您的需求是与不相关的类一起工作的东西,而mixin会使它们变得相关。
编辑:好吧,由于C++类型系统的工作方式,mixin解决方案将严格生成不相关的类。不过,我还是选择模板函数解决方案。
我想提出一个替代方案,但经常被遗忘,因为它是鸭子类型和接口的混合体,很少有语言提出这一壮举(注意:实际上非常接近Go对接口的理解)。
// 1. Ask for a free function to exist:
void toString(std::string& buffer, SomeClass const& sc);
// 2. Create an interface that exposes this function
class ToString {
public:
virtual ~ToString() {}
virtual void toString(std::string& buffer) const = 0;
}; // class ToString
// 3. Create an adapter class (bit of magic)
template <typename T>
class ToStringT final: public ToString {
public:
ToStringT(T const& t): t(t) {}
virtual void toString(std::string& buffer) const override {
toString(buffer, t);
}
private:
T t; // note: for reference you need a reference wrapper
// I won't delve into this right now, suffice to say
// it's feasible and only require one template overload
// of toString.
}; // class ToStringT
// 4. Create an adapter maker
template <typename T>
ToStringT<T> toString(T const& t) { return std::move(ToStringT<T>(t)); }
现在呢?享受
void print(ToString const& ts); // aka: the most important const
int main() {
SomeClass sc;
print(toString(sc));
};
这两个阶段有点重量级,但它提供了惊人的功能:
- 没有硬接线数据/接口(多亏了鸭子打字)
- 低耦合(得益于抽象类)
而且易于集成:
- 您可以为已经存在的接口编写一个"适配器",并从OO代码库迁移到更灵活的代码库
- 您可以为一组已经存在的重载编写一个"接口",并从泛型代码库迁移到更集群的代码库
除了锅炉板的数量之外,你如何从两个世界中无缝地选择优势真的很神奇。
我在写这个问题时的一些想法:
支持模板函数的参数:
- 函数可以重载,因此可以处理第三方类型和内置类型
支持mixin的论点:
- 同质语法:添加的行为与任何其他成员函数一样被调用。然而,众所周知,C++类的接口不仅包括其公共成员函数,还包括在这种类型的实例上操作的自由函数,因此这只是美学上的改进
- 通过向mixin添加一个非模板基类,我们获得了一个接口(在Java/C#意义上),可以用来处理提供行为的所有对象。例如,如果我们使
ToFile<T>
继承自FileWritable
(声明一个纯虚拟toFile
成员函数),我们就可以拥有FileWritable
的集合,而不必求助于复杂的异构数据结构
关于用法,我认为函数模板在C++中更为惯用。
- CMake:我们可以为一组不形成可执行文件或库的特定文件指定包含目录吗?
- 0 的 2D 数组 不相关循环的破坏逻辑 - C++
- 为什么数组到指针的输出不相关
- 将积分类型的数组作为另一个不相关的积分类型的阵列进行访问的安全且符合标准的方法
- 如何在不实例化派生类的情况下管理一组派生类枚举
- 继承是否是将一组模型参数传递给不同类的可行解决方案
- 匹配匹配任何内容,直到'%'不后跟'%',后跟一组字符
- 在 c++ 中将 find_first_of 与字符串一起使用,而不是一组预定义字符
- 如何在不与标准库运算符冲突的情况下为一组相关类重载运算符?
- 为什么弦乐在代表时不会移至下一组字符
- 视觉C++库,用于在不使用Opencv的情况下将一组图像转换为视频
- 数组初始化影响看似不相关的类C++SDL
- C++ 中 15000 数组索引之后的不相关整数标准输入
- openCV中一组点的形状检测(不使用图像)
- 从一组集合中找出所有不相交集合的算法是什么
- C++,要求用户输入一组数字求和(未知的输入数量),但是数字不会加起来
- 为什么 count 不返回一组C++对象的正确值?
- 如何使用数组通过引用对对象进行排序?(以一种不那么愚蠢/复杂的方式.)
- 我应该更喜欢mixin还是函数模板来将行为添加到一组不相关的类型中
- 存储一组不重叠的范围,并严格查找一个值是否存在于任何一个范围中