为什么C++更喜欢使用模板方法而不是方法重载?

13

假设我有两个类,第一个用于写入原始类型(boolintfloat等),第二个扩展第一个以便也能写入复杂类型:

struct Writer {
    virtual void Write(int value) = 0;
};

struct ComplexWriter : public Writer {
    template <typename TValue> void Write(const TValue &value) {
        boost::any any(value);
        Write(any);
    }
    //virtual void Write(int value) = 0; // see question below
    virtual void Write(const boost::any &any) = 0;
};

这个想法是,如果有人调用myWriter.Write(someIntValue);,整数重载将优先于模板方法。

然而,我的编译器(Visual C++ 11.0 RC)总是选择模板方法。例如,下面的代码片段将在控制台打印Wrote any

struct ComplexWriterImpl : public ComplexWriter {
    virtual void Write(int value) { std::cout << "Wrote an int"; }
    virtual void Write(const boost::any &any) { std::cout << "Wrote any"; }
};

void TestWriter(ComplexWriter &writer) {
    int x = 0;
    writer.Write(x);
}

int main() {
    ComplexWriterImpl writer;
    TestWriter(writer);
}

当我也在 ComplexWriter 类中声明了 Write(int) 方法时(请参见第一个代码片段中被注释的行),行为突然改变。然后它会将 Wrote an int 打印到控制台。

这是我的编译器应该表现的方式吗?C++ 标准是否明确规定只有在同一类中定义的重载方法(而不是基类)将优先于模板方法?


2
我没有看到你在哪里使用一个带有模板的方法来重载一个接受int的方法。你有两个不相关的类,Writer有一个接受int的方法,而ComplexWriter有一个带有模板的方法(和一个接受boost::any const&的方法)。在你的测试代码中,你使用了ComplexWriter,所以它当然会调用ComplexWriter的成员。如果你真的期望它调用完全不相关的类Writer的成员,你能解释一下为什么(以及如何)应该这样做吗? - celtschk
2
你提到 ComplexWriter 扩展了 Writer,但是在你列出的代码中它并没有继承自 Writer。这是正确的设置吗? - Attila
谢谢指出。是的,ComplexWriter应该派生自Writer。我已经相应地更新了问题。结果仍然相同(尽管忘记指定基类连接到我的问题的核心。疼痛)。 - Cygon
2个回答

7
问题在于当你调用writer.Write(x)时,编译器看到的是ComplexWriter而不是ComplexWriterImpl,因此它只知道ComplexWriter中定义的函数-模板函数和boost::any函数。 ComplexWriter没有包含任何接受int的虚拟函数,因此它没有办法调用在ComplexWriterImpl中定义的整数重载函数。
当你添加ComplexWriter类的虚拟重载函数时,编译器就会意识到ComplexWriter类中有一个整数重载,并因此调用它在ComplexWriterImpl中的实现。
编辑:既然你已经编辑了ComplexWriterWriter之间的继承关系,我可以给你提供更完整的解释:
当你创建一个子类并在其中定义一个函数时,无论其参数类型如何,该名称的所有基类函数都将被隐藏。
我相信你可以通过使用using关键字来绕过这个问题。
struct ComplexWriter : public Writer {
    template <typename TValue> void Write(const TValue &value) {
        boost::any any(value);
        Write(any);
    }
    using Writer::Write;
    virtual void Write(const boost::any &any) = 0;
};

欲了解更多详情,请参阅此FAQ条目:http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9

编辑2:仅确认此解决方案确实解决了您的问题:http://ideone.com/LRb5a


是的,我在代码片段中忘记指定基类了。ComplexWriter确实继承自Writer,但我观察到的行为正如我所解释的那样,即使正确指定了基类。 - Cygon
@Cygon 是的,注意到了。我现在只是试图想出一个解释/编辑。如果失败了,我可能会删除这个答案! - obmarg
无论如何,感谢您的快速回复!忘记指定基类真是一个愚蠢的错误,因为它扭曲了整个问题 :) - Cygon
不错!我已经写了几年的C++,以前从未遇到过这种类型的using语句。即使有多个重载,在我的GCC和MSVC上也能产生所需的结果。 - Cygon

2
当您通过“ComplexWriter”“接口”访问对象时,编译器将尝试使用该类中的定义来解析对“Write(int)”函数的调用。如果不能这样做,它将考虑基类。
在这种情况下,您有两个候选项:“Write(any)”和模板版本。由于此时没有明确的“Write(int)”可用,因此必须在这两个选项之间进行选择。 “Write(any)”需要隐式转换,而模板版本不需要,因此调用模板版本(进而调用“Write(any)”)。
要使来自“Writer”的“Write(int)”可用,请导入“Writer::Write”函数:
class ComplexWriter : public Writer
{
  using Writer::Write;
  // rest is as before
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接