C++ 我应该选择mixin还是函数模板来向一组不相关的类型添加行为?

C++ 我应该选择mixin还是函数模板来向一组不相关的类型添加行为?,c++,oop,templates,mixins,C++,Oop,Templates,Mixins,mixin和函数模板是为大量类型提供行为的两种不同方式,只要这些类型满足某些要求 例如,假设我想编写一些代码,允许我将一个对象保存到一个文件中,只要该对象提供toString成员函数(这是一个相当愚蠢的示例,但请耐心等待)。第一种解决方案是编写如下所示的函数模板: template <typename T> void toFile(T const & obj, std::string const & filename) { std::ofstream file

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");
模板
void toFile(T常量和对象,std::string常量和文件名)
{
std::流文件(文件名);

filePro函数模板:耦合更松散。您不需要从任何东西派生来获得新类中的功能;在您的示例中,您只实现了
toString
方法,就是这样。您甚至可以使用有限形式的,因为没有指定
toString
的类型

Pro-mixin:严格来说,什么都没有;您的需求是与不相关的类一起工作,而mixin使它们变得相关


< > > >编辑< <强>:好的,由于C++类型系统的工作方式,MIXIN解决方案将严格地生成无关的类。不过,我将使用模板函数的解决方案。

< P>第一种方法更灵活,因为它可以与任何类型的任何类型的工作一起转换为<代码> STD::String < /C>(这可以通过使用traits类实现)而无需修改该类型。您的第二种方法总是需要修改类型以添加功能。

我在撰写此问题时的一些想法:

支持模板函数的参数:

  • 函数可以重载,因此可以处理第三方和内置类型
支持混合的参数:

  • 同构语法:添加的行为与任何其他成员函数一样被调用。然而,众所周知C++类的接口不仅包括它的公共成员函数,而且还包括在这种类型的实例上运行的自由函数,因此这只是一种美学上的改进。
  • 通过向mixin添加一个非模板基类,我们获得了一个接口(在Java/C意义上),可以用来处理提供行为的所有对象。例如,如果我们使
    ToFile
    继承自
    filewriteable
    (声明一个纯虚拟
    ToFile
    成员函数),我们可以拥有
    文件可写的
    集合,而不必求助于复杂的异构数据结构

关于用法,我认为函数模板在C++中更为习惯。

< P>我想提出一种替代方案,通常被遗忘,因为它是鸭子打字和接口的混合体,而且很少有语言提出这一壮举(注:实际上非常接近GO的接口)。 这两个阶段有点重,但它提供了惊人的功能:

  • 无硬接线数据/接口(感谢duck键入)
  • 低耦合(由于抽象类)
而且易于集成

  • 您可以为已经存在的接口编写“适配器”,并从OO代码库迁移到更灵活的接口
  • 您可以为已经存在的一组重载编写“接口”,并从通用代码库迁移到更集群的代码库

除了锅炉板的数量之外,您如何无缝地从这两个方面获得优势,这真是令人惊讶。

mixin如何访问派生类的受保护成员?如果继承是另一种方式,这是可能的,但那就没那么有用了(除非我误解了什么?)@LucTouraille:ehm,说得好。Thinko就我而言,删除了这个。我不确定用同一个mixin注入的两个类是相关的说法是否正确:想想[boost::operators](www.boost.org/doc/libs/release/libs/utility/operators.htm)mixin;您会说由于这个mixin而提供公共运算符的所有类都是相关的吗?即使从语言的角度来看,它们也不是真正相关的:它们彼此不了解,也不共享一个公共基类(
mixin
mixin
是不相关的).我同意更松散的耦合:函数模板和类型之间存在单向依赖,而mixin和类型之间的依赖是双向的(因此无法处理第三方和内置类型)@ LuxToaLe:好的,这两个类在C++类型系统中是不相关的,但我认为这仅仅是因为模板是如何工作的。在更广泛的OOP意义上,我会说它们是(弱)。因为它们共享一个通用的模板基类。如果以前使用的方法不是所有C++程序都是这样的,那么语法是唯一的;特别是C++ 11在标准库中引入了一系列新的模板函数来处理容器和迭代器。@拉斯曼:你们是对的,许多C++库(不仅仅是标准库)。采用泛型编程范式,因此广泛使用自由函数。更好的是,将第一个修改为
toString(obj)
,而不是
obj.toString()
。然后您可以重载
toString
来序列化任何内容,包括非类类型和无法添加成员的类。我肯定这可能是因为时间太晚了,但您能否详细说明一下这会给您带来什么“选项1”@MarkB:这里的主要思想是利用接口代码(松耦合)。ToString
基类的用户只与该基类绑定。依赖关系管理通常是大型项目中的一个问题,这种方法在不牺牲duck类型优点的情况下解决了这个问题。
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");
// 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));
};