Meta Mixin ..这甚至是一回事吗? (模板元编程)

时间:2018-11-16 00:43:52

标签: c++ c++11 mixins template-meta-programming crtp

我想提出一个“基于混合蛋白的结构模式”(这甚至是一个术语吗?),但我不确定它是否会在“某些情况下”出现。 基本思想是生成乘以继承mixins的“使用模板类的类型”。所以类型声明看起来像:typedef BaseType<Mixin1, Mixin2, MixinN> Type1; 该方法的一些成就:

  • Type1的特殊功能(如运算符重载和构造函数重载)始终可用。
  • 显式类型转换开销由BaseType提取。
  • C ++多个implicit conversion barrier没问题。

通常的模板混合方法形式here如下: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;
}

所以,我的问题:

  • 在需要操作员重载/隐式转换功能的情况下,是否可以实际使用此功能?
  • 似乎需要虚拟继承吗?
  • 还有其他类似的实现方式(我的搜索失败)吗?
  • 最后,有没有一种叫做“元混合”的东西可以提供类型的特殊功能?

编辑:针对Phil1970的回答:

我将从问题3的答案开始。

  • 这种方法导致阶级扩散:我完全同意。我必须承认的一大缺点。
  • 增加耦合。不确定如何增加耦合。 * 1
  • 其余标记在那里,我认为由于StringType相当final的事实,因此不适用。并且StringType不知道或不是真正的混合类。 * 1

现在回答第1个问题。

  • 通常最好避免隐式转换。
  • 只要final,剩下的事就可以了。 * 2

之前的问题消失了(非常感谢Phil),提出了新的问题。

  • * 1:它仅是一个标头,StringStyle不依赖于mixin,我认为没有理由这样做。当然,如果有必要,它可以使用私有头。那么如何执行耦合?
  • * 2:只是想发表意见或让我更正。

非常感谢。

1 个答案:

答案 0 :(得分:-1)

您的问题:

  • 通常最好避免隐式转换。同样,如果不添加很多单行函数,就无法通过这种方法重用std::string运算符,例如+,+ =。包装类除了为您提供更多的转换外没有任何其他好处,因为您随后将使用新的字符串类型并使用mixin方法,这甚至更糟,因为您还需要在自己的类型之间进行转换。
  • 您为什么要使用虚拟继承?您是否真的要从多个具有共同基础并且具有自己数据的类中衍生出来。
  • 由于这是一个糟糕的设计,因此您可能找不到很多人这样做。您的设计会增加耦合,导致类激增,增加类型转换,并使维护变得更加困难。
  • 我相信,没有这样的事情。

对于上述简单功能,首选的方法是定义一个名称空间(或者,如果您有很多可以通过某种方式进行分类的功能(例如,文件名操作),则可以命名),然后在其中包含自由功能。

通过使用名称空间,您具有一些优点:

  • 如果您调用许多函数,则始终可以在函数或源文件中添加using语句(永远不要在头文件中)。
  • 自动建议可以很好地找到这些功能。

如果某些原始mixin保持状态,那么您应该执行一个辅助类。对于诸如HTML构建器之类的类可能就是这种情况,该类可能具有可用于创建HTML文档的功能,例如AddTag,Add Attribute,AddEncodedUrl等。

此方法的一大优点是,耦合比设计中的宽松得多。例如,一个文件对(标题和源)将包含用于打印机的所有功能。如果需要,您不必创建使用混合混合的新类。

您的方法的一个大问题是,随着时间的流逝,您将有很多不同的StringType<…>如果您可以使用5个mixin,则您有2 ^ 5 = 32个类。到那时,几乎可以肯定的是,您经常会需要不包含的mixin,如果调用得很深,您会得到级联的更改。而且,如果您在所有地方都使用模板,则编译速度会变慢,并且可能会使代码膨胀。

在大多数情况下,大多数专家也认为,最好避免隐式转换。如果您在多个类之间进行了多次转换,那么在某些时候您将产生意想不到的转换或歧义。明确进行某些转换可以限制该问题。通常最好使用显式转换,就像std::string中的专家所做的那样。如果要使用C样式字符串,则必须调用成员函数c_str()

例如,由于您的StringType类同时定义了对const char *QString的转换,因此,如果您有一个接受两者的方法(可能是一个Append函数),则您有一个冲突。

如果您确实要进行转换,请改用命名方法(例如AsQString()c_str()tostdstring() ...)。它有助于确保进行所有转换。这样可以更轻松地找到它们,并且像在代码中的某些位置所做的那样,最好进行显式强制转换。尽管static_cast和其他强制类型转换有时很有用,但是在重构代码时也可能隐藏一些问题,因为在某些情况下,强制类型转换可能会在编译时不正确。如果您强制转换为派生类,并且在某个时候决定将派生类更改为其他内容而忘记更新某些强制类型,则会是这种情况。

您应该为应用程序选择最合适的字符串,并在需要时进行转换。在大型应用程序中,您可能在UI中使用一种类型(例如CString或QString),而在跨平台或与第三方库共享的库中使用标准字符串。有时这些库也有自己的字符串类。您的选择应尽量减少无用的转换。