在基类没有虚方法的派生类中声明虚方法是错误的吗?

时间:2016-04-04 03:48:34

标签: c++ inheritance

例如:

// No virtual methods in Base
class Base
{
public:
  Base() {}
  ~Base() {}
  void Foo();
};

// Derived class containing virtual methods -- will this cause problems??
class Derived : public Base
{
public:
  Derived() {}
  virtual ~Derived() {}
  virtual void Bar() {}
};

我已经读过,在Base类中声明至少一个虚拟(和/或纯虚拟)函数将隐式地使所有Derived类将virtual关键字应用于Base中定义的那些相同(虚拟)方法。 。这是有道理的。

昨天我读了answer的评论,其中@ Aaron-McDaid表示:

  

...如果你有一个非虚拟的Base类,并且你的Derived类中有一些虚方法,那么Base * b = new Derived();删除b;将是未定义的行为,可能会崩溃你的程序。它看起来很安全,但事实并非如此。 (这是因为b不会指向Derived对象的'start' - 它将被vtable所需的空间所抵消。然后,删除将不会在与新的完全相同的地址上运行,因此它是不是一个有效的免费地址。如果你打算在任何地方使用虚拟方法,那么就把虚拟方法放在基地里。

这对我来说听起来似乎有道理 - 有没有人确切知道它是否准确?如果它是准确的,为什么C ++不会隐含地使基本方法(例如析构函数)虚拟化以保持vtable在前面并防止未定义的行为?

是否有人不希望这种情况发生?

编辑: 如果基类不包含虚方法,在派生类中声明(和使用)虚方法是否安全?

2 个答案:

答案 0 :(得分:3)

  

在基类没有虚方法的派生类中声明虚方法是错误的吗?

不,这不是错误。这是安全的,只要你不使用错误的地址delete对象,这可能发生在Aaron讨论的场景中......

  是否有人确切知道[Aaron-McDaid的断言]是否准确?

是和否。 "这是真的。 Base *b = new Derived(); delete b;将是未定义的行为,可能会导致程序崩溃,

为什么在某些系统上的解释是正确的,但是标准没有指定虚拟调度的实现机制,它只规定了编译器编写者的行为必须协调。没有什么特别的理由认为指向虚拟调度表的指针将位于对象的前面。不过,你可以编写一个程序来查看它是否在你自己的系统上:

#include <iostream>

struct Base
{
    int b_, b2_, b3_;
    Base() { std::cout << "Base(this " << (void*)this << ")\n"; }
};

struct Derived : Base
{
    int d_, d2_, d3_;
    Derived() { std::cout << "Derived(this " << (void*)this << ")\n"; }
    virtual ~Derived() { }
};

int main()
{
    Base* p = new Derived();
    std::cout << "p " << (void*)p << '\n';
}

使用GCC / Linux,我看到......

Base(this 0x12eda018)
Derived(this 0x12eda010)
p 0x12eda018

...显示Base子对象偏移8个字节到Derived:小于3 int s的大小,这意味着它别的:如Aaron所描述的那样,在那里假设一个虚拟的调度指针是合理的。

另外,&#34;如果您要在任何地方使用虚拟方法,那么在基地放置一个虚拟方法。&#34; 是特定的必要建议方案段落与...; delete b;代码一起引入,但不应该被误认为是具有virtual函数的所有派生类的必要性:只有那些可能delete - 通过Base*

  

为什么C ++没有隐式地使基本方法(例如析构函数)虚拟化以保持vtable在前面并防止未定义的行为?

向类中添加虚拟函数consequences可能是不合需要的,概括为:

  • 额外的内存使用
  • 性能下降
  • 可以限制/阻止来自不同进程的共享内存中对象的安全使用
  • 妥协封装,因为有人可以避免在基类析构函数中执行操作
  • 可能会阻止内存布局与协议或硬件兼容

基础课可以有很多原因,包括&#34;混合课程&#34;提供额外的功能,[模板]&#34;政策&#34;控制某些功能方面并可能暴露一些API用于运行时控制和/或查询,对派生类的实现支持等。在这些情况下,通常不需要基类中的虚拟析构函数,并添加一个毫不含糊地会带来上述缺点。

答案 1 :(得分:2)

在非虚拟析构函数的情况下,只会调用基类的析构函数,如果在派生类中有基类的成员未定义/未设置,那导致崩溃(尽管并非总是如此)

在多重继承的情况下,引用中提到的情况是可能的,因为给定的基类可以有不同的偏移量。从内存结构中可以看出,继承的类包含在类中。

 _____________________________
| Base class 1                |
 _____________________________
| Base class 2                |
|    .......                  |
 -----------------------------

在上述情况下,如果您尝试使用基类2指针删除或执行任何操作,则除非从对象地址获取偏移量,否则可能会遇到此问题。对于Base class 1指针,您不应该遇到任何问题。

C ++不会使其隐含,因为继承可能有不同的目的。而且,虚函数具有性能损失。有时,人们不需要虚拟析构函数,因为基类只用于取出一些常见的东西而基类不用于引用对象(通过OOP的概念,这可能不正确)。与其他语言不同,C ++还支持私有和受保护的继承。使用派生对象作为基类没有任何意义。

不希望使用虚函数的示例是私有继承,这意味着派生类是根据基类而不是基类实现的。例如可以使用数组实现堆栈。或者,您可以在Stack中拥有数组对象或私有派生数组。

class Stack: private Array {
  //Implement function using Array
}

显然,Stack在这种情况下不再是Array。因此,不需要在Array中使用虚拟析构函数。即使您使用,也不能因为私有继承而释放Stack。 还有许多其他案例可能。

在您的情况下,您可能不会遇到崩溃,但派生类的成员将无法获得自由并为您提供资源泄漏。