Curiously Recurring Template Pattern (CRTP) 是正确的解决方案吗?

Is the Curiously Recurring Template Pattern (CRTP) the right solution here?

场景

考虑一个 class 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); ... }
    ...
};

进一步考虑一个 subclass 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(),当直接调用时,现在支持两个 classes 中的 either 提供重载的任何参数。

但是,FooLogger::writeLine() 仅支持 class Logger 具有 write() 重载的参数类型...它看不到额外的 write() 在 class FooLogger.

中声明的重载

希望它能看到它们,这样它也可以用这些参数类型调用!

当前解决方案

我使用 Curiously Recurring Template Pattern (CRTP) 让它工作:

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> {
    ...
};

(self-contained example program at Ideone)

虽然它完成了工作,但代价是增加了代码的复杂性和冗长:

  1. 它使基础 class 的实现明显更难阅读(参见 Ideone link),更难维护(一定不要忘记做 static_cast 舞蹈将来在适当的时候向 class 添加更多代码!)
  2. 需要将 AbstractLoggerLogger 分成两个 class。
  3. 因为 base-class 现在是一个 class 模板,它所有成员函数的实现现在必须包含在头文件中(而不是 .cpp 文件)-即使是那些不需要做 static_cast 事情的人。

问题

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

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

(请注意,googletest 有太多方法:您可以在 class 中提供 PrintTo() 方法,或者您可以重载 operator<<,如果两者都满足,则首选 PrintTo()可用。这样做的好处是,您可以 以不同于 的方式序列化为日志记录,而不是序列化为典型的输出流(例如,您的 class 已经有一个 operator<< '为日志做你想做的事)。

(魔法全部包含在gtest-printer.h - see class UniversalPrinter at line 685中用于触发。)

这还有一个优点,即很容易添加任何 class/struct/object 以正确记录,甚至无需费心扩展日志记录 class。此外......如果有人扩展记录器class(即从它派生)以序列化class AAA,并且在不同的代码段中有不同的推导序列化会发生什么class BBB 然后最后你写了一些你想记录 AAAs 和 BBBs 的代码?派生的 class 方法在那里效果不佳 ...

反之亦然:

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>...