这里的异常重复模板模式(CRTP)是否是正确的解决方案?

时间:2019-01-09 22:46:03

标签: c++ overloading crtp

场景

考虑一个类Logger,该类具有一个为标准C ++类型重载的成员函数write(),并且还具有一些方便的函数模板,例如writeLine(),它们在内部调用write()

class Logger {
  public:
    void write(int x) { ... }
    void write(double x) { ... }
    ...

    template <typename T>
    void writeLine(T x) { write(x); ... }
    ...
};

进一步考虑一个子类FooLogger,该子类为域特定类型添加了额外的write()重载(我们将其中两个称为FooType1FooType2):

class FooLogger : public Logger {
  public:
    using Logger::write;

    void write(FooType1 x) { ... }
    void write(FooType2 x) { ... }
    ...
};

self-contained example program at Ideone

问题

现在,直接调用

FooLogger::write()时,支持两个类中的 提供重载的任何参数。

但是,FooLogger::writeLine()仅支持类Logger具有write()重载的参数类型...它看不到在类{中声明的其他write()重载{1}}。

想要查看它们,以便也可以使用这些参数类型来调用它!

当前解决方案

我使用好奇重复模板模式(CRTP)来工作:

FooLogger

self-contained example program at Ideone

虽然可以做到,但代价是增加了代码的复杂性和可读性:

  1. 这使基类的实现明显难以阅读(请参见Ideone链接),并且难以维护(一定要在将更多代码添加到类中时在适当的地方做template <typename TDerivedClass> class AbstractLogger { ... template <typename T> void writeLine(T x) { static_cast<TDerivedClass*>(this)->write(x); ... } }; class Logger : AbstractLogger {} class FooLogger : public AbstractLogger<FooLogger> { ... }; 舞步。未来!)
  2. 它需要将static_castAbstractLogger分成两个类。
  3. 由于该基类现在是一个类模板,因此现在必须将其所有成员函数的实现都包含在标头(而不是Logger文件)中,甚至不需要执行这些操作。 .cpp事。

问题

考虑到上述情况,我正在寻求具有C ++经验的人的见解:

  • CRTP是这项工作的正确工具吗?
  • 还有另一种解决方法吗?

2 个答案:

答案 0 :(得分:4)

另一种方式:

template <typename ...Ts>
class Logger : private Ts...
{
public:
    using Ts::write...;

    void write(int x) { /*...*/ }
    void write(double x) { /*...*/ }
    // ...

    template <typename T>
    void writeLine(T x) { write(x); /*...*/ }
    // ...
};

class FooWriter
{
public:
    void write(FooType1 x) { /*...*/ }
    void write(FooType2 x) { /*...*/ }
};
using FooLogger = Logger<FooWriter>;

然后使用任何(或它们的别名):

Logger<>Logger<FooWriter>Logger<FooWriter, BarWriter> ...

答案 1 :(得分:1)

为什么不使用在您的类型和记录器的流输出类型上定义的自由函数,例如operator<<,或者仅使用可见的调用函数?有关如何执行此操作的示例:编写了googletest,以便您可以通过提供序列化方法来自定义所有断言。请参见Teaching Googletest How To Print Your Values,然后您可以查看实现以查看其操作方式。

(请注意,googletest也有方法:您可以在您的类中提供PrintTo()方法,也可以重载operator<<,如果两者均可用,则首选PrintTo()。这具有优势与序列化为典型输出流相比,您可以序列化为不同地(例如,您的类已经有一个operator<<了,它并没有做您想要的日志操作)。

(魔法全部包含在gtest-printer.h中-有关触发器,请参见line 685class UniversalPrinter。)

这还具有一个优点,即添加任何要正确记录的类/结构/对象非常容易,而无需费心扩展记录类。此外...如果有人扩展记录器类(即从它派生)以序列化类AAA,会发生什么,而在另一段代码中,有一个不同的派生序列化类BBB,然后最后,您在要同时记录AAABBB的地方编写了一些代码?派生类方法在这里效果不佳...