C++开发中级


881 浏览 5 years, 4 months

5.2 名称冲突以及歧义基类

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

名称冲突以及歧义基类

多重继承崩溃的场景并不难以想象,下面的示例显示了一些必须予以考虑的边缘情况:

1.歧义名称

如果Dog类以及Bird类都有一个名为eat()的方法,会发生什么?由于Dog类以及Bird类毫不相干,eat()方法的一个版本无法重写另一个版本—在子类DogBird中这两个方法都存在。

只要客户代码不调用eat()方法,就不会出现问题。尽管有两个版本的eat()。
DogBird类仍然可以正确编译。然而,如果客户代码试图调用DogBird的eat()方法,编译器将报错,指出eat()的调用有歧义。编译器不知道该调用哪个版本。下面的代码存在歧义错误:

class Dog{
    public:
        virtual void bark(){
            cout << "Wolf!" << endl;
        }       
        virtual void eat(){
            cout << "The dog has eaten" << endl;
        }
};

class Bird{
    public:
        virtual void chirp(){
            cout << "Chirp!" << endl;
        }       
        virtual void eat(){
            cout << "The bird has eaten" << endl;
        }
};

class DogBird : public Dog, public Bird{};

int main(){
    DogBird myConfuseAnimal;
    myConfuseAnimal.eat(); // BUG, Ambiguous call to method eat()
}

为了消除歧义,可以显式地将对象向上转型(本质上是向编译器隐藏多余的方法版本),也可以使用消除歧义语法。下面的代码显示了调用eat()方法的Dog版本的两种方案:

    static_cast<Dog>(myConfuseAnimal).eat(); // Slices, calling Dog::eat()
    myConfuseAnimal.Dog::eat();  // calls Dog::eat()

通过使用与访问父类方法相同的语法(::运算符),子类的方法本身可以显式地将具有相同名称的不同方法消除歧义。例如,DogBird类可以定义自己的eat()方法,从而消除其他代码中的歧义错误。在方法内部,可以判断调用那个父类版本:

class DogBird : public Dog, public Bird{
    public: 
        virtual void eat(){
            Dog::eat(); //Explictly call Dog's version of eat()
        }
};

另一种引起歧义的情况是从同一个类继承两次。例如,如果出于某种原因Bird从Dog继承,DogBird的代码将无法编译,因为Dog变成了歧义基类。

class Dog(){};
class Bird : public Dog() {};
class DogBird : public Bird, public Dog {}; // BUG! Dog is an ambiguous base class

多数歧义基类的情况或者是由人为的“what-if',示例(如前面的示例)引起的,或者是由于类层次结构的混乱引起的。图中显示了前面示例中的类图表,并指出了歧义。

数据成员也可以引起歧义。如果Dog以及Bird具有同名的数据成员,当客户代码试图访问这个成员是就会发生歧义错误。

2.歧义基类

多个父类本身具有共同的父类是一种很可能出现的情况。例如,可能Bird以及Dog都是Animal类的子类,如下图所示。

C++允许这种类型的类层次结构,尽管仍然存在着名称的歧义。例如,如果Animal类有一个public方法sleep(), DogBird对象无法调用这个方法,因为编译器不知道调用Dog继承的版本还是Bird继承的版本。

使用“菱形”类层次结构的最佳方法是将最顶部的类设置为抽象类,所有方法都设置为纯虚方法。由于类只声明方法而不提供定义,在基类中没有方法可以调用,因此在这个层次上就没有歧义。

下面的示例实现了菱形类层次结构,其中有一个纯虚方法eat(),每个子类都必须定义这个方法。DogBird类仍然需要显式地说明使用哪个父类的eat()方法,但是Dog以及Bird引起歧义的原因是它们具有相同的方法,而不是因为从同一个类继承。

class Animal{
    public:
        virtual void eat() = 0;
};

class Dog : public Animal{
    public:
        virtual void bark(){
            cout << "Wolf!" << endl;
        }       
        virtual void eat(){
            cout << "The dog has eaten" << endl;
        }
};

class Bird : public Animal{
    public:
        virtual void chirp(){
            cout << "Chirp!" << endl;
        }       
        virtual void eat(){
            cout << "The bird has eaten" << endl;
        }
};

class DogBird : public Dog, public Bird{
    public: 
        virtual void eat(){
            Dog::eat();
        }
};

虚基类是处理菱形层次结构顶部类的更好方法