C++开发中级


1011 浏览 5 years, 10 months

1.2 重写方法

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

重写方法

从某个类继承的主要原因是为了添加或者替换功能。Sub类定义在父类基础上添加了功能,提供了额外的someOtherMethod()方法。另一个方法someMethod()从Super继承而来,这个方法的行为在子类中与在超类中相同。在许多情况下您可能想要替换或者重写某个方法来修改类的行为。

1.将所有方法都设置为virtual,以防万一

在C++中,重写(override)方法有一点别扭,因为必须使用关键字virtual。只有在超类中声明为virtual的方法才能被子类正确地重写。virtual关键字出现在方法声明的开头,下面显示了Super的修改版本:

class Super
{
    public:
        Super();
        virtual void someMethod();
    protected:
        int mProtectedInt();
    private:
        int mPrivateInt;
};

virtual关键字有些微妙,经常被当作语言的设计不当部分。经验表明最好将所有方法都设置为virtual。这样一来,就不必担心重写方法是否可以运行,这样做唯一的缺点是对性能具有轻微的影响。virtual关键字会贯穿本章,在后面将进一步讨论性能。

即使Sub类不大可能被扩展,最好还是将这个类的方法设置为virtual,以防万一

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

从根据经验,为了避免遗漏virtual关键字引发的问题,可将所有方法设置为virtual(包括析构函数,但不包括构造函数)。

2.重写方法的语法

为了重写某个方法,需要在子类定义中重新声明这个方法,就像在超类中声明的那样,并在子类的实现文件中提供新的定义。例如,Super类包含了一个名为someMethod()的方法,在Super.cpp中提供的someMethod()定义如下:

void Super::someMethod()
{
    cout << "This is Super's version of someMethod()." << endl;
}

注意在方法定义中不需要重复使用virtual关键字。

如果您希望在Sub类中提供someMethod()的新定义,首先应该在Sub类定义中添这个方法,如下所示:

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(); //Overrides Super's someMethod()
        virtual void someOtherMethod();
};

someMethod()方法的新定义与Sub的其他方法一并在Sub.cpp中给出:

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

一旦将方法或者析构函数标记为virtual,它们在所有子类中就一直是virtual,即使在子类中删除了virtual关键字也同样如此。例如,在下面的Sub类中,someMethod()仍然是virtual,并且可以被Sub的子类重写,因为在Super类中已经将其标记为virtual 。

class Sub : public Super
{
    public:
        Sub();
        void someMethod(); //Overrides Super's someMethod()
        virtual void someOtherMethod();
};
3.客户对于重写方法的看法

经过前面的改动之后,其他代码仍然可以用先前的方法调用someMethod(),可以用Super类的对象也可以使用Sub类的对象调用这个方法。然而,现在someMethod()的行为将根据对象所属类的不同而变化。
例如,下面的代码与先前一样可以运行,调用Super版本的someMethod():

Super mySuper;
mySuper.someMethod(); // call Super's version of someMethod().

这段代码的输出为

This is Super's version of someMethod().

如果声明一个Sub类的对象,将自动调用子类版本:

Sub mySub;
mySub.someMethod(); // call Sub's version of someMethod().

这段代码的输出为:

This is Sub's version of someMethod().

Sub类对象的其他方面维持不变。从Super继承的其他方法仍然保持Super提供的定义,除非在Sub中显式地将这些方法重写。

您在前面己经知道,指针或者引用可以指向某个类的对象或者子类的对象。对象本身“知道”自己所属的类,因此只要这个方法为声明为virtual,就会自动调用对应的方法。例如,如果您有一个对Super引用,而实际引用的是Sub对象,调用someMethod()实际上会调用子类版本,如下面的示例所示。

如果在超类中省略了virtual关键字,重写功能将无法正确运行。

Sub mySub;
Super& ref = mySub;
ref.someMethod(); // call Sub's version of someMethod().

记住,即使超类的引用或者指针知道这实际上是一个子类,也无法访问没有在超类中定义的子类方法或者成员。下面的代码无法编译,因为Super引用中没有一个称为someOhterMethod()的方法:

Sub mySub;
Super & ref=mySub;
mySub.someOtherMethod();  // This is fine
ref.someOtherMethod(); //BUG

非指针非引用对象无法正确处理子类的特征信息。您可以将Sub转换为Super,或者将Sub赋值给Super,因为Sub是一个Super。然而,此时这个对象将遗失子类的所有信息:

Sub mySub;
Super assignedObject = mySub; //assign a Sub to a Super
assignedObject.someMethod(); // call Super's version of someMethod()

为了记住这个看上去有点奇怪的行为,可以考虑对象在内存中的状态。将Super对象当作占据了一块内存的盒子。Sub对象是稍微大一点的盒子,因为它拥有Super的一切,还添加了一点内容。对于Sub的引用或者指针,这个盒子并没有变—只是可以用新的方法访问它。
然而,如果将Sub转换为Super,就会为了适应较小的盒子而扔掉Sub类所有的“特性”。

超类的指针或者引用指向子类对象时,子类保留其重写方法。但是如果通过类型转换将子类对象转换为超类对象,此时会丢失其特征。重写方法以及子类数据的丢失就是所谓的截断(slicing) 。

4.禁用重写(仅限C++11)

C++11允许将方法标记为final,这意味着无法在子类中重写这个方法。试图重写final()方法将导致编译器错误。考虑下面的Super类:

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

在下面的Sub类中重写someMethod()会导致编译器错误,因为someMethod()在Super类中被标记为final 。

class Sub : public Super
{
    public:
        Sub();
        virtual void someMethod(); //Error
        virtual void someOtherMethod();
}

参考代码 Sub\SubOverride.cpp