在 C++ 中,重定义继承而来的 non-virtual(非虚)函数通常是不推荐的,原因如下:
-
隐藏父类的实现:如果在派生类中重定义了一个非虚函数,这将隐藏父类中具有相同名称和参数的函数。这意味着即使通过基类的指针或引用调用该函数,也会调用派生类中的版本,这可能违反了多态性原则,导致无法调用预期的基类函数。
-
破坏封装:重定义非虚函数可能会导致派生类与基类之间的耦合增强,因为派生类需要知道基类的具体实现细节,并且在派生类中复制这些细节。这违反了封装的原则,使得代码难以维护和理解。
-
意图不明确:在派生类中重定义非虚函数可能会使类的设计意图变得不清晰。虚函数表明存在意图进行重写(override),而非虚函数通常表示不打算被重写。如果需要在派生类中提供不同的行为,应该使用虚函数。
-
多态行为的缺失:非虚函数不支持运行时多态,这意味着即便派生类中有与基类同名的函数,通过基类指针或引用调用该函数时,总是会调用基类的实现,而不是派生类的实现。这可能导致派生类的行为不符合预期。
-
维护难度增加:如果在派生类中重定义了基类的非虚函数,任何对基类函数的修改都需要在所有派生类中进行检查和相应的修改,这增加了代码的维护难度。
-
引起混淆:重定义基类中的非虚函数可能会使得阅读和理解代码的人员产生混淆,特别是在有多级继承时,理解哪个版本的函数被调用变得更加复杂。
如果确实需要在派生类中更改基类中某个函数的行为,应该考虑以下方案:
- 将基类函数声明为虚函数(virtual),并在派生类中重写(override)它。
- 如果不希望在基类中提供默认实现,可以将基类函数声明为纯虚函数(pure virtual)。
- 如果派生类需要提供一个完全不同的函数,可以考虑给该函数取一个不同的名称,以避免与基类函数混淆。
让我们通过一个简单的例子来说明为什么不建议在派生类中重定义继承而来的 non-virtual 函数。
假设我们有一个基类 Base
和一个从 Base
继承的派生类 Derived
。在基类中,我们有一个非虚拟函数 foo()
,在派生类中我们重定义了这个函数。
#include <iostream>
class Base {
public:
void foo() {
std::cout << "Base::foo()" << std::endl;
}
};
class Derived : public Base {
public:
void foo() {
std::cout << "Derived::foo()" << std::endl;
}
};
int main() {
Base b;
Derived d;
// 直接调用
b.foo(); // 输出: Base::foo()
d.foo(); // 输出: Derived::foo()
// 通过基类指针调用
Base* ptr = &d;
ptr->foo(); // 输出: Base::foo()
return 0;
}
在上面的例子中,即使 ptr
指向的是 Derived
类型的对象,调用 ptr->foo()
时还是执行了 Base
类的 foo
方法,而不是 Derived
类的 foo
方法。这是因为 foo
被定义为非虚函数,不支持多态。
现在,假设我们想要实现多态行为,我们需要将 Base
类中的 foo()
方法声明为虚函数,并在派生类中重写它:
#include <iostream>
class Base {
public:
virtual void foo() {
std::cout << "Base::foo()" << std::endl;
}
};
class Derived : public Base {
public:
void foo() override { // 使用 override 关键字明确表示重写
std::cout << "Derived::foo()" << std::endl;
}
};
int main() {
Base b;
Derived d;
// 直接调用
b.foo(); // 输出: Base::foo()
d.foo(); // 输出: Derived::foo()
// 通过基类指针调用
Base* ptr = &d;
ptr->foo(); // 输出: Derived::foo()
return 0;
}
在这个修改后的例子中,由于 foo
现在是虚函数,并且在派生类中被重写,ptr->foo()
调用将执行 Derived
类的 foo
方法,展现了多态行为。
重定义可能会隐藏基类实现并导致预期外的行为,而将函数声明为虚函数并在派生类中重写可以实现正确的多态行为。