Должен ли я предпочесть миксины или шаблоны функций для добавления поведения к набору несвязанных типов? - PullRequest
4 голосов
/ 18 января 2012

Миксины и шаблоны функций - это два разных способа предоставления поведения широкому набору типов, если эти типы удовлетворяют некоторым требованиям.

Например, давайте предположим, что я хочу написать некоторый код, которыйпозволяет мне сохранять объект в файл, если этот объект предоставляет функцию-член 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");

Что такоеплюсы и минусы этих двух подходов?Есть ли любимый, и если да, то почему?

Ответы [ 4 ]

5 голосов
/ 18 января 2012

Первый подход гораздо более гибкий, поскольку его можно использовать для работы с любым типом, который обеспечивает любой способ преобразования в std::string (это может быть достигнуто с использованием классов признаков) без необходимостиизменить этот тип .Ваш второй подход всегда требует модификации типа для добавления функциональности.

3 голосов
/ 18 января 2012

Профессиональные шаблоны функций: муфта ослаблена. Вам не нужно ничего делать, чтобы получить функциональность в новом классе; в вашем примере вы реализуете только метод toString и все. Вы даже можете использовать ограниченную форму утка, набрав , так как тип toString не указан.

Pro mixins: ничего, строго; ваше требование - что-то, что работает с несвязанными классами, и миксины заставляют их стать связанными.

Редактировать : Хорошо, благодаря тому, как работает система типов C ++, решение mixin будет строго создавать несвязанные классы. Я бы пошел с решением функции шаблона, хотя.

2 голосов
/ 18 января 2012

Я хотел бы предложить альтернативу, о которой часто забывают, потому что она представляет собой сочетание типизации утки и интерфейсов, и очень немногие языки предлагают это умение (примечание: на самом деле очень близко к интерфейсу 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 на более гибкую
  • Вы можете написать «интерфейс» для уже существующего набора перегрузок и перейти от общей базы кода к более кластерной

Помимо количества котлов, поразительно, как вы без проблем выбираете преимущества обоих миров.

0 голосов
/ 18 января 2012

Несколько мыслей, которые у меня возникли при написании этого вопроса:

Аргументы в пользу шаблонных функций:

  • Функция может быть перегружена, поэтому третьеМожно обрабатывать сторонние и встроенные типы.

Аргументы в пользу миксинов:

  • Однородный синтаксис: добавленное поведение вызывается как любойдругие функции-члены.Однако хорошо известно, что интерфейс класса C ++ включает не только его открытые функции-члены, но также и свободные функции, которые работают с экземплярами этого типа, так что это просто эстетическое улучшение.
  • Добавлениене шаблонный базовый класс для mixins, мы получаем интерфейс (в смысле Java / C #), который можно использовать для обработки всех объектов, обеспечивающих поведение.Например, если мы сделаем ToFile<T> наследуемым от FileWritable (объявляя чисто виртуальную toFile функцию-член), мы можем получить коллекцию FileWritable, не прибегая к сложным гетерогенным структурам данных.

Что касается использования, я бы сказал, что шаблоны функций более идиоматичны в C ++.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...