在基没有虚拟方法的派生类中声明虚拟方法是错误的吗
Is it an error to declare a virtual method in a derived class whose base does not have a virtual method?
例如:
// 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() {}
};
我读过在基类中声明至少一个虚拟(和/或纯虚拟)函数会隐式地导致所有派生类将virtual关键字应用于在基类中定义的那些相同(虚拟)方法。这是有道理的。
昨天,我读到一条回复的评论,@Aaron McDaid表示:
如果你有一个非虚拟基类,并且你的Derived类中有一些虚拟方法,那么Base*b=new Derived();删除b;将是未定义的行为,并可能使您的程序崩溃。它看起来很安全,但事实并非如此。(这是因为b不会指向Derived对象的"开始"-它将被vtable所需的空间所偏移。然后,删除将不会在与新地址完全相同的地址上操作,因此它不是一个可释放的有效地址。如果你要在任何地方使用虚拟方法,那么在基中放置一个虚拟d’tor。
这在我看来似乎是合理的——有人确切知道它是否准确吗?如果它是准确的,为什么C++不隐式地使一个基本方法(例如析构函数)成为虚拟的,以保持vtable在前面并防止未定义的行为?
有没有一个例子不希望这种情况发生?
编辑:如果基类不包含虚拟方法,那么在派生类中声明(和使用)虚拟方法是否安全?
在基没有虚拟方法的派生类中声明虚拟方法是错误的吗?
不,这不是错误。这是安全的,只要你不使用错误的地址delete
对象,这可能发生在Aaron讨论的场景中。。。
有人确切地知道[亚伦·麦克达德的断言]是否准确吗?
是和否。确实,"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
子对象向Derived
偏移8字节:这小于3个int
s的大小,这意味着它是其他东西:正如Aaron所描述的,假设虚拟调度指针在那里是合理的。
另外,"如果你要在任何地方都有虚拟方法,那么就在基中放一个虚拟d’tor。"对于...; delete b;
代码中引入的特定场景是必要的建议,但不应被误认为是所有具有virtual
函数的派生类的必要:只有那些可能通过Base*
进行delete
操作的派生类。
为什么C++不隐式地使一个基本方法(例如析构函数)成为虚拟的,以保持vtable在前面并防止未定义的行为?
将虚拟函数添加到类中可能会产生不希望出现的后果,概括地说:
- 额外内存使用
- 较慢的性能
- 可以限制/阻止安全使用来自不同进程的共享内存中的对象
- 折衷封装,因为有人可以避免基类析构函数中的操作
- 可能会妨碍内存布局与协议或硬件的兼容性
基类存在的原因有很多,包括作为"混合体"提供额外的功能,[模板]"策略"控制某些功能方面,并可能公开一些API用于运行时控制和/或查询,对派生类的实现支持等。在这些情况下,通常不需要在基类中使用虚拟析构函数,不加区别地加一个会带来上述缺点。
在非虚拟析构函数的情况下,只会调用基类的析构函数,如果派生类中有基类的成员未定义/未设置,则会导致崩溃(尽管情况并非总是如此)。
引用中提到的情况在多重继承的情况下是可能的,因为给定基类可能有不同的偏移量。从内存结构来看,继承的类包含在类中。
_____________________________
| Base class 1 |
_____________________________
| Base class 2 |
| ....... |
-----------------------------
在上面的情况下,如果您试图使用基类2指针来删除或执行任何操作,那么您可能会面临这个问题,除非您从对象地址获取偏移量。对于基类1指针,您不应该遇到任何问题。
C++并没有使它隐式化,因为继承可能有不同的目的。此外,虚拟函数还具有性能惩罚。有时,人们不需要虚拟析构函数,因为基类只用于取出一些常见的东西,而基类不用于引用对象(根据OOP的概念,这可能是不正确的)。与其他语言不同,C++还支持私有和受保护的继承。使用派生对象作为基类没有任何意义。
不想使用虚拟函数的例子是私有继承,这意味着派生类是根据基类而不是基类实现的。例如,Stack可以使用数组来实现。或者,您可以在Stack中拥有数组的对象,也可以私下派生数组。
class Stack: private Array {
//Implement function using Array
}
显然,在这种情况下,Stack
不再是Array
。因此,在Array
中不需要使用虚拟析构函数。即使使用,由于私有继承,也无法释放Stack
。还有许多其他可能的情况。
在您的情况下,您可能不会面临崩溃,但派生类的成员将无法获得自由,并导致资源泄漏。
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?
- 如何编写 operator= 用于使用虚拟方法与非平凡成员的匿名联合
- 让编译器告诉什么确切的纯虚拟方法使结构抽象?
- 使用模板而不是虚拟方法的管道模式
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 从纯虚拟类 (A) 派生的指针无法访问来自纯类 (B) 的重载方法
- 为什么调用没有正文的纯虚拟方法不会导致链接器错误?
- 出于什么目的,非虚拟方法将与C++一起使用?
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 如何重写继承的嵌套类中存在的虚拟方法
- 私有虚拟方法有什么用?
- 派生类中纯虚拟基方法的专业化
- 基类可以声明虚拟方法但不定义它吗?仍然在派生类中定义
- googletest:测试基类具有纯虚拟方法的派生类时的核心转储
- 确保模拟的 GTest 方法覆盖虚拟方法
- 使用回调函数从构造函数调用虚拟/派生方法的替代方法?
- 如何调用孩子的方法:虚拟关键字不起作用