【问题标题】:C++ base class function call from last derived in diamond design钻石设计中最后派生的 C++ 基类函数调用
【发布时间】:2015-04-22 19:26:35
【问题描述】:

我正在学习 C++,在阅读和测试了很多关于多重继承、虚拟继承/方法和菱形设计之后,我仍然有一些问题要理解它直到最后。

我在菱形设计模式中,B类和C类继承virtual public A,D类继承public B, public C:

    A
  /   \
 B     C
  \   /
    D

所有类都实现了一个私有变量std::string _message,我使用以下字符串对其进行初始化。

 "class-name instance"

只有Class A 也实现了virtual public display(void) const 方法。

void     display(void) const{
     std::cout << this->_message << std::endl;
}

主要是:

D foo;
foo.display();

输出是:

A instance

但是,当我使用 Xcode 逐步“检查”元素调试时,我看到在 D 实例中,我可以正确地找到一个 B、一个 C 和 A 对象(只有一个由 B 和 C 共享)以及所有不同的 @ 987654331@ 正确分配。

我做错了什么?我需要覆盖 D 类中的display() 方法吗?但是如果是这样,如果我必须在派生类中重新实现相同的方法,那么多重继承的真正意义是什么?

【问题讨论】:

  • 我听说过“钻石问题”、“可怕的钻石”和“死亡钻石”。我不认为我以前见过有人称它为“钻石设计模式”......

标签: c++ inheritance virtual diamond-problem


【解决方案1】:

您的问题似乎与菱形图案无关,但通常与 C++ 中的继承模型有关。 C++ 中的大多数东西都是静态绑定的,因此在编译时编译器会修复使用了某个名称的方法或成员:

如果您访问隐式 this 对象的成员或使用指向对象的指针或引用,您最终将访问指针或引用在编译时具有的类的成员,无论是否在运行时是具有同名成员的派生类对象。这就是为什么当你在派生类中定义一个同名成员时,他们说你不能覆盖,而只能shadow基类中的成员。

非虚函数也是如此。

唯一表现不同的是虚函数。这些函数可以在派生类中被覆盖,并且传递给基类指针的代码可以虚拟基类的函数并最终执行派生类中给出的实现。

因此,虚函数的重点不是让编译器在派生类的上下文中重新解释某个函数(正如您似乎理解的那样),而是使用 派生类中的不同函数。

回到你的动机:如果你想要一个 display 函数来打印取决于实际对象类型的消息,固定的东西是 printig,它不需要是虚拟的。但是你需要一个虚函数来获取对象类型。像这样:

#include <iostream>
#include <ostream>

class A {
public:
    void display() { std::cout << get_message() << '\n'; }
    virtual const char * get_message() { return "A instance"; }
};

class B : virtual public A {
public:
    virtual const char * get_message() { return "B instance"; }
};

class C : virtual public A {
public:
    virtual const char * get_message() { return "C instance"; }
};

class D : public B, public C {
public:
    // virtual const char * get_message() { return B::get_message(); }
    // virtual const char * get_message() { return C::get_message(); }
    // virtual const char * get_message() { return "D instance"; }
};

int main(void)
{
    D foo;
    foo.display();
    A* a_ptr = &foo;
    a_ptr->display();
    return 0;
}

给出的示例将无法编译(现在这是由于菱形图案),因为编译器无法决定,应该在 D 对象中选择 A::get_message() 的哪个覆盖器,B::get_message() 或 C::get_message(),您需要在 D 中制作其中一个 cmets 真正的代码来为 D 声明唯一的get_message,在其中可以重复使用现有的功能。

【讨论】:

  • 谢谢! “因此,虚函数的重点不是让编译器在派生类的上下文中重新解释某个函数(正如您似乎理解的那样),而是使在基类中的函数替换为不同的函数成为可能派生类。”这句话让我明白了一切。我也可以通过将 display() 实现为调用 getter 的普通成员函数(在 A 类中声明 virtaul 并在 D 类中覆盖“。所以在主要方面:D foo; foo.dispaly() 将输出“ D 实例”。
猜你喜欢
  • 1970-01-01
  • 2015-08-18
  • 2016-08-30
  • 2016-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-16
  • 2011-01-08
相关资源
最近更新 更多