我正在编写一组用于自定义序列化和反序列化的类。我想我会使用<<和>>运营商,因为他们一般传达这种意义让我们从一个用于处理写入泛型流的类开始。
class Writer
{
public:
virtual void writeBytes(const void* p, size_t n) = 0;
template <typename T> void write(const T& v)
{
writeBytes(&v, sizeof(v));
}
template <typename T> Writer& operator<<(const T& v)
{
write(v);
return *this;
}
};
然后有一个可序列化的接口,即提供它自己的序列化方法。
class Serializable
{
public:
virtual Writer& serialize(Writer& writer) const = 0;
};
Writer& operator<<(Writer& writer, const Serializable& s)
{
s.serialize(writer);
return writer;
}
最后,我写了一个如何使用它的例子:一个可序列化的缓冲区。
class SerializableBuffer : public Serializable
{
public:
SerializableBuffer() : data_(NULL), length_(0) { }
SerializableBuffer(void* data, size_t length) : data_(data), length_(length) { }
virtual Writer& serialize(Writer& writer) const
{
writer.writeBytes(data_, length_);
return writer;
}
private:
void* data_;
size_t length_;
};
所以这是有趣的部分。显然,如果我使用方法调用,它就完全符合它的预期。但是使用&lt;&lt;运营商正在展示一些怪癖。我的第一次尝试是做以下事情:
unsigned char input[] = { 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0x12, 0x34 };
SerializableBuffer sb(input, sizeof(input));
unsigned char d[8];
BufferWriter writer(buffer(d));
writer << sb;
这会失败,因为输出缓冲区不够大。如果我添加一个printf,事实证明它在Writer类中调用模板!这是更奇怪的部分,以下是作品。
unsigned char input[] = { 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0x12, 0x34 };
SerializableBuffer sb(buffer(input));
unsigned char d[8];
BufferWriter writer(buffer(d));
writer << (Serializable&)sb;
我猜测模板引擎正在赢得一个后代?任何人都可以解释这里发生了什么,为什么?
答案 0 :(得分:2)
非常简单 - 它只是遵循重载决策规则。当第一个参数是operator <<
或子类时,您有两个可能匹配的重载Writer &
:
template <typename T> Writer& Writer::operator<<(const T& v);
Writer& operator<<(Writer& writer, const Serializable& s);
当第二个参数是SerializableBuffer
时,第一个参数可以完全匹配,而第二个参数可以与转换匹配。由于完全匹配更好,第一次匹配。
当第二个参数是Serializable
时,两者都完全匹配,所以第二个参数更好,因为它不是模板。
如果要在参数是Serializable
的子类时使模板不匹配,可以使用enable_if
:
template <typename T>
std::enable_if<!std::is_base_of<Serializable, T>::value, Writer &>::type
operator<<(const T &v)
将导致模板无法为Serializable
的任何子类实例化。