Meta Mixin .. это даже вещь? (Шаблон мета-программирования) - PullRequest
0 голосов
/ 16 ноября 2018

Я хочу представить «структуру структуры на основе миксинов» (это даже термин?), Но не совсем уверен, выдержит ли она «некоторую ситуацию». Основная идея состоит в том, чтобы генерировать «тип, использующий шаблонный класс», который умножает наследуемые миксины Таким образом, объявление типа будет выглядеть так: typedef BaseType<Mixin1, Mixin2, MixinN> Type1; Некоторые достижения подхода:

  • Type1, такая как перегрузки оператора и перегрузки конструктора, всегда доступна.
  • Явные накладные расходы на приведение типов абстрагируются от BaseType.
  • C ++ множественный неявный барьер преобразования не проблема.

Обычный шаблонный подход здесь выглядит так: template<class Base> class Printing : public Base {...}. Главный недостаток для меня при таком подходе:

  • Необходимо явным образом привести Printing к Base, чтобы использовать некоторые специальные функции Base, или необходимо явно предоставить эти перегрузки (я знаю, что это просто вопрос строки кода) , Но в некоторых ситуациях это будет раздражать.

Именно поэтому у меня возникла идея создать базу. Пожалуйста, посмотрите на реализацию («некоторая ситуация»):

#include <iostream>
#include <functional>

#ifdef QT_CORE_LIB
#include <QString>
#endif


template<template<class> class... mixin_t>
class StringType : public mixin_t<StringType<mixin_t...>>...
{
    std::string _value;

public:
    StringType() : _value("") {}

    StringType(const StringType &other) = default; // Copy

    StringType(StringType &&other) = default; // Move

#ifdef QT_CORE_LIB
    StringType(const QString &value) { this->_value = value.toStdString(); }
#endif

    StringType(const std::string &value) { _value = value; }

    StringType(const char *value) { _value = value; }

    template<template<class> class T>
    StringType(const StringType<T> &value)
    {
        _value = static_cast<const std::string &>(value);
    }


    StringType &operator=(const StringType &rhs) = default; // copy assign
    StringType &operator=(StringType &&rhs) = default; // Move assign


#ifdef QT_CORE_LIB
    operator QString() const { return QString::fromStdString(_value);}
#endif

    operator std::string() const { return _value; }

    operator const char *() const{ return _value.c_str(); }
};




template<class this_t> struct _empty_mixn {};

template<class this_t> struct ToStringMixin
{
    this_t toString() const { return *static_cast<const this_t *>(this); }
};

template<class this_t> struct StringPrinterMixin
{
    void print() const
    {
        std::cout << "From the printer: " << *static_cast<const this_t *>(this);
    }
};




typedef StringType<_empty_mixn> String;
typedef StringType<ToStringMixin> Message;
typedef StringType<ToStringMixin, StringPrinterMixin> PrinterAttachedString;




int main()
{
    Message msg1(String("msg1\n"));
    std::cout << msg1;
    std::cout << "toString() : " << msg1.toString();

    Message msg2 = String("msg2\n");
    std::cout << msg2;
    std::cout << "toString() : " << msg2.toString();

    Message msg3(std::string("msg3\n"));
    std::cout << msg3;
    std::cout << "toString() : " << msg3.toString();

    Message msg4 = std::string("msg4\n");
    std::cout << msg4;
    std::cout << "toString() : " << msg4.toString();

    Message msg5("msg5\n");
    std::cout << msg5;
    std::cout << "toString() : " << msg5.toString();

    Message msg6 = "msg6\n";
    std::cout << msg6;
    std::cout << "toString() : " << msg6.toString();

    std::cout << "\n---------------------\n\n";

    PrinterAttachedString str1(String("str1\n"));
    std::cout << str1;
    std::cout << "toString() : " << str1.toString();
    str1.print();

    PrinterAttachedString str2 = String("str2\n");
    std::cout << str2;
    std::cout << "toString() : " << str2.toString();
    str2.print();

    PrinterAttachedString str3(std::string("str3\n"));
    std::cout << str3;
    std::cout << "toString() : " << str3.toString();
    str3.print();

    PrinterAttachedString str4 = std::string("str4\n");
    std::cout << str4;
    std::cout << "toString() : " << str4.toString();
    str4.print();

    PrinterAttachedString str5("str5\n");
    std::cout << str5;
    std::cout << "toString() : " << str5.toString();
    str5.print();

    PrinterAttachedString str6 = "str6\n";
    std::cout << str6;
    std::cout << "toString() : " << str6.toString();
    str6.print();

    return 0;
}

Итак, мои вопросы:

  • Было бы целесообразно использовать это в ситуации, когда необходима функция перегрузки оператора / неявного приведения?
  • Кажется, возникнет необходимость виртуального наследования?
  • Есть ли какая-либо другая реализация, подобная этой (мой поиск был неудачным)?
  • Наконец, есть ли вещь, называемая "meta mixin", которая обеспечит специальные функции типа?

Редактировать: В ответ на ответ Phil1970:

Я собираюсь начать с ответа на вопрос 3.

  • Этот подход ведет к распространению класса: я полностью согласен. Я должен признать один большой недостаток.
  • Увеличивает сцепление. Не уверен, как это увеличивает сцепление. * 1
  • Остатки, отмеченные там, я считаю, неприменимы из-за того, что StringType вполне final. И StringType не знает или о смешанном классе по-настоящему. * 1

Теперь ответ на вопрос № 1.

  • Обычно лучше избегать неявного преобразования.
  • Остальные для меня в порядке, пока это final. * 2

С исчезновением предыдущего вопроса (огромное спасибо Филу) возникли новые вопросы.

  • * 1: Это только один заголовок, StringStyle не зависит от миксинов, и я не вижу причин для этого. И, конечно, это может использовать закрытый заголовок, если это как-то станет необходимо. Тогда как оно усиливает связь?
  • * 2: Просто искать мнения или поправить меня.

Большое спасибо.

1 Ответ

0 голосов
/ 16 ноября 2018

На ваш вопрос:

  • Обычно лучше избегать неявного преобразования. Кроме того, вы не сможете повторно использовать std::string операторов, таких как +, + =, с таким подходом, не добавляя многострочную функцию. Класс-обертка не принесет вам ничего, кроме добавления дополнительных преобразований, так как вы будете использовать новый тип строки, а при использовании подхода mixin это даже хуже, так как вам также нужно преобразовывать собственные типы.
  • Зачем вам использовать виртуальное наследование? Вы действительно хотите получить из нескольких классов, которые имеют общую базу и имеют свои собственные данные.
  • Поскольку это плохой дизайн, вы, вероятно, не найдете много людей, делающих это. Ваша конструкция увеличивает сцепление, приводит к увеличению класса, увеличению числа типов преобразований и усложняет техническое обслуживание.
  • Полагаю, такой вещи не существует.

Для простых функций, подобных описанным выше, предпочтительным подходом было бы определение пространства имен (или многих, если у вас много функций, которые можно каким-то образом классифицировать, например, манипулирование именами файлов), а затем иметь свободные функции внутри него.

Используя пространство имен, вы получаете несколько преимуществ:

  • Если вы вызываете много функций, вы всегда можете добавить оператор использования внутри вашей функции или исходного файла (никогда в заголовочном файле).
  • Автоматическое предложение будет хорошо работать, чтобы найти эти функции.

Если некоторые из оригинальных миксинов поддерживают состояние, то вам следует сделать вспомогательный класс. Это может иметь место для такого класса, как конструктор HTML, в котором могут быть такие функции, как AddTag, Add Attribute, AddEncodedUrl и т. Д., Которые можно использовать для создания документа HTML.

Одним большим преимуществом этого подхода является то, что сцепление намного слабее, чем в вашей конструкции. Например, файловая пара (заголовок и источник) будет содержать все функции, используемые для принтера. Если вам это нужно, вам не нужно создавать новый класс, который использует какую-то комбинацию mixin.

Одна большая проблема с вашим подходом состоит в том, что со временем у вас будет много разных StringType<…> Если у вас есть 5 миксинов, которые можно использовать, у вас есть 2 ^ 5 = 32 класса. В этот момент почти наверняка вам понадобится миксин, который вы не включили, и тогда у вас будет каскадное изменение, если вызов будет глубоким. И если вы будете использовать шаблон повсеместно, у вас будет замедление компиляции и, возможно, некоторый раздувшийся код.

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

Например, поскольку ваш класс StringType определяет преобразование в const char * и QString, то, если у вас есть метод, который принимает оба (возможно, функцию Append), тогда у вас конфликт.

Если вы действительно хотите преобразование, используйте вместо этого именованный метод (например, AsQString(), c_str(), tostdstring() ...). Это помогает гарантировать, что все преобразования предназначены. Это облегчает их поиск, и, конечно, лучше использовать явное приведение, как вы сделали в нескольких местах в вашем коде. Хотя static_cast и другие приведения иногда полезны, они также могут скрывать некоторые проблемы, когда код подвергается рефакторингу, как в некоторых случаях, приведение может скомпилироваться, хотя оно и не является правильным. Это будет иметь место, если вы приведете к производному классу и в какой-то момент решите заменить производный класс на что-то другое и забудете обновить некоторые приведения.

Вы должны выбрать наиболее подходящую строку для вашего приложения и выполнить преобразование при необходимости.В больших приложениях вы можете использовать один тип для пользовательского интерфейса (например, CString или QString) при использовании стандартной строки в библиотеках, которые совместно используются платформами или сторонней библиотекой.Некоторое время у этих библиотек есть свой собственный класс строк.Ваш выбор должен попытаться минимизировать бесполезные конверсии.

...