C++开发中级


1026 浏览 5 years, 10 months

6.1 修改重写方法的特征

版权声明: 转载请注明出处 http://www.codingsoho.com/

修改重写方法的特征

重写某个方法的主要原因是修改方法的实现。然而,有时是为了修改方法的其他特征。

1.修改方法的返回类型

根据经验,重写方法要使用与超类一致的方法声明(或者方法原型)。实现可以改变,但原型保持不变。

然而事实上未必总是如此,在C++中,如果原始的返回类型是某个类的指针或者引用,重写的方法可以将返回值改变为子类的指针或者引用。这就是所谓的协变返回类型(covariant return types)。

如果超类和子类处于平行层次结构(parallel hierarchy)中,使用这个特性可以带来方便。平行层次结构就是说,一个层次结构与另一个类层次结构没有相交,但是存在联系。

例如,考虑樱桃果园模拟程序。您可以使用两个类层次结构模拟不同但是明显相关的实际对象。

Cherry基类有一个名为BingCherry的子类。与此类似,另一个层次结构的基类为CherryTree,子类为BingCherryTree。下图显示了这两个类层次结构。

现在假定CherryTree类有一个名为pick()的方法,这个方法从树上获取一个樱桃:

Cherry* CherryTree::pick(){
    return new Cherry();
}

在BingChetryTree子类中,您可能想要重写这个方法。或许冰樱桃在摘下来的时候需要擦拭。由于BingCherry是一个Cherry,在下面的示例中,方法的原型保持不变,而方法被重写。BingCherry指针自动转换为Cherry指针。

Cherry* BingCherryTree::pick(){
    BingCherry* theCherry = new BingCherry();
    theCherry->polish();
    return theCherry;
}

上面的实现非常好,这也是想要使用的方法。然而,由于您知道BingCherryTree始终返回BingCherry对象,因此可以通过修改返回类型向这个类的潜在用户指明这一点,如下所示:

BingCherry* BingCherryTree::pick(){
    BingCherry* theCherry = new BingCherry();
    theCherry->polish();
    return theCherry;
}

为了判断能否修改重写方法的返回类型,可以考虑已有代码是否能够继续运行。在前面的示例中,修改返回类型没有问题,因为假定pick()方法总是返回Cherry*的代码仍然可以成功编译并正常运行。因为BingCherry是一个Cherry,因此任何根据CherryTree版本的pick()返回值调用的方法仍然可以基于BingCherryTree版本的pick()的结果进行调用。

您不能将返回类型修改为完全不相关的类型,例如void*。下面的代码无法编译,因为编译器认为您试图重写CherryTree::pick(),但是无法区分BingCherryTree的pick()方法以及CherryTree的pick()方法,因为无法用返回类型消除方法的歧义。

void* BingCherryTree::pick(){ // BUG!
    BingCherry* theCherry = new BingCherry();
    theCherry->polish();
    return theCherry;
}

这段代码会导致编译错误,类似于

'BingCherryTree::pick': overriding virtual return type differs and is not covariant from'CherryTree::pick'。
2.修改方法的参数

如果在子类的定义中使用父类虚方法的名称,但参数与父类中同名方法的参数不同,那么这不是重写父类的方法一一这是创建一个新方法。回到本章前面的Super以及Sub示例,您可以试着在Sub中使用新的参数列表重写someMethod()方法,如下所示:

class Super
{
    public:
        Super();
        virtual void someMethod();
};

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(int i); // Compiles, but doesn't override
        virtual void someOtherMethod();
};

这个方法的实现如下所示:

void Sub::someMethod(int i){
    cout << "This is Sub's version of someMethod with argument " << i << "." << endl;
}

前面的类定义可以编译,但没有重写someMethod()方法。因为参数不同,您创建的是一个只存在于Sub中的新方法。如果您想要让一个名为someMethod()的方法采用int参数,并且只将这个方法应用于Sub类对象,前面的代码没有问题。

实际上,C++标准指出当Sub定义了这个方法的时候,原始的方法被隐藏。下面的代码无法编译,因为没有参数的someMethod()方法不再存在。

Sub mySub;
mySub.someMethod();

您可以使用一种较为晦涩的技术兼顾二者。也就是说,您可以使用这一技术在子类中有效地用新的原型“重写”某个方法,同时还可以继承该方法的超类版本。这一技术使用using关键字显式地在子类中包含这个方法的超类定义:

class Sub : public Super
{
    public:
        Sub();
        using Super::someMethod; // Explicitly "inherits" the Super version
        virtual void someMethod(int i); // Add a new version of someMethod
        virtual void someOtherMethod();
};

子类的方法与超类方法同名但参数不同的情况很少见

参考代码 Sub\SubOverrideMethodKeyUsing.cpp

override关键字(仅限C++11)

有时候,可能会偶然创建一个新的虚方法,而不是重写超类的方法。考虑下面的Super以及Sub类,其中Sub类正确地重写了SomeMethod():

class Super
{
    public:
        Super();
        virtual void someMethod(double d);
};

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(double d);
        virtual void someOtherMethod();
};

可以通过引用调用SomeMethod(),如下所示:

    Sub mySub;
    Super & ref = mySub;
    ref.someMethod(1); // calls Sub's version

代码将正确地调用Sub类重写的SomeMethod()。

现在假定重写SomeMethod()时,您使用整数(而不是双精度)做参数,如下所示:

class Super
{
    public:
        Super();
        virtual void someMethod(double d);
};

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(int i);
        virtual void someOtherMethod();
};

在前面的小节中己经看到,这些代码没有重写SomeMethod(),而是创建了一个新的虚方法。如果您试图像下面的代码那样通过引用调用SomeMethod(),将会调用Super的SomeMethod()而不是Sub的中定义的那个方法。

    Sub mySub;
    Super & ref = mySub;
    ref.someMethod(1); // calls Super's version

如果修改了Super类但忘记更新所有子类,就会发生这类问题。例如,或许Super类的第一个版本具有一个名为SomeMethod()、采用整数做参数的方法。然后在Sub子类中重写了SomeMethod()方法,仍然用整数做参数。后来发现Super类中的SomeMethod()方法需要一个双精度数而不是整数,因此更新了Super类中的SomeMethod()。

此时您可能忘记了更新子类中的SomeMethod(),让它们接受双精度数而不是整数。由于忘记了这点,实际上就是创建了一个新的虚方法而不是正确地重写了这个方法。

可以用override关键字避免这种情况,如下所示:

class Super
{
    public:
        Super();
        virtual void someMethod(double d);
};

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(int i) override; // [Error] 'virtual void Sub::someMethod(int)' marked override, but does not override
        virtual void someOtherMethod();
};

参考代码 Sub\SubOverrideMethodKeyOverride.cpp

Sub的定义将导致编译器错误,因为override关键字表明您要重写Super类的SomeMethod()方法,但在Super类中的SomeMethod()方法只接收双精度数,而不接受整数。

[Error] 'virtual void Sub::someMethod(int)' marked override, but does not override