前言

函数隐藏C++继承中容易引起误解的知识,函数隐藏又和函数覆盖(或函数重写)又有些类似,但又不一样。

简单记录于此,方便自己查阅。

正文

函数隐藏

函数隐藏是指当派生类中定义了与基类同名的成员函数时,无论参数列表是否相同、返回值类型是否一致,基类中所有同名函数在派生类作用域内均变为不可见。

静态绑定!

条件
  1. 同名即隐藏:只要函数名相同,基类函数即被屏蔽,与参数类型、个数、顺序及返回值无关。

  2. 跨作用域特性:隐藏发生在基类与派生类这两个不同的作用域之间,区别于同一作用域内的函数重载。

  3. 静态绑定:隐藏是编译期行为,不涉及虚函数表与运行时多态机制。

派生类中定义一个同名函数,会隐藏基类中该函数的所有重载版本。

例子
例子1
class Base {
public:
    void func() {}
    void func(int x) { }
    void func(double x) {}
};
class Derived : public Base {
public:
    void func(float x) {} // 仅定义一个同名函数
};
//使用
Derived d;
d.func();       //  编译错误:Base::func() 被隐藏
d.func(10);     //  编译错误:Base::func(int) 被隐藏
d.func(3.14);   //  编译错误:Base::func(double) 被隐藏
d.func(1.0f);   //  正确:调用 Derived::func(float)

知识点:编译器在派生类作用域中找到 func 名称后,立即停止向基类作用域查找,因此基类中所有 func 重载均不参与后续的重载决议。

例子2

当基类函数被隐藏后,仍可通过以下方式访问被隐藏函数的方法。

  1. using 声明

    (推荐):在派生类中显式引入基类函数,恢复重载集。

    class Derived : public Base {
    public:
        using Base::func; // 引入基类所有func重载
        void func(float x) { }
    };
    // 此时 d.func()、d.func(10) 均可正常调用
  1. 作用域解析符

    临时显式调用特定基类版本

    d.Base::func(10); // 仅调用 Base::func(int),不恢复重载集

函数覆盖

函数覆盖(函数重写)是C++实现运行时多态的核心机制。它特指派生类重新定义基类的虚函数,使得通过基类指针或引用调用该函数时,实际执行的是派生类的版本。这与编译期绑定的函数隐藏有着本质区别。

动态绑定!

条件
  1. 基类函数为虚函数:基类中必须使用 virtual 关键字声明。

  2. 函数签名完全一致:函数名、参数列表(类型、个数、顺序)必须完全相同。

  3. 返回值类型兼容:返回值类型必须相同,或满足协变返回类型规则(即派生类返回类型是基类返回类型的指针或引用的派生类)。

  4. 访问权限非私有:基类虚函数不能是 private,否则派生类无法访问,无法构成覆盖。

例子
例子1
class Base {
public:
    virtual void func(int x) { }
    virtual void func() { }
};

class Derived : public Base {
public:
    void func(int x) override {} // 正确:覆盖基类虚函数
    void func() override { }       // 正确:覆盖基类虚函数
    void func(double x) override { } // 编译错误!这不是覆盖,是隐藏
};

override 的三大作用

  • 防止误写为隐藏:如果派生类函数签名与基类虚函数不匹配,编译器直接报错,避免意外隐藏。

  • 防止基类函数被删除:如果基类虚函数被删除或重命名,派生类的 override 函数会编译失败,及时暴露问题。

  • 提高代码可读性:明确表达"此函数意图覆盖基类虚函数",便于维护。

override 是 C++11 引入的关键字,用于显式标记覆盖函数,提供编译期安全检查

例子2
class Base {
public:
    virtual Base* clone() { return new Base(); }
};

class Derived : public Base {
public:
    Derived* clone() override { return new Derived(); } // 协变返回类型,合法覆盖
};

注意:协变返回类型仅适用于指针和引用,不适用于值返回。值返回时类型必须完全一致。

陷阱
  • 忘记 virtual:基类函数未声明为虚函数,派生类同名同参函数仅构成隐藏,无法多态。

  • 签名不一致:参数类型、const 限定符、引用限定符等细微差异导致隐藏而非覆盖。

  • private 虚函数:基类虚函数为 private 时,派生类仍可覆盖(因为覆盖发生在类定义阶段,而非调用阶段),但无法通过基类指针在类外调用。

  • 析构函数未虚:基类析构函数非虚时,通过基类指针删除派生类对象会导致未定义行为。

隐藏 vs 覆盖

对比维度函数隐藏函数覆盖(函数重写)
基类函数要求无特殊要求(虚/非虚均可)必须为 virtual 虚函数
函数签名要求仅函数名相同即可函数名、参数列表、返回值必须完全一致
绑定方式编译期静态绑定运行期动态绑定(多态)
作用域关系跨作用域屏蔽跨作用域替换
访问方式Base::func() 显式调用通过基类指针/引用自动调用派生类版本

核心记忆点:覆盖是多态,隐藏是屏蔽。覆盖看签名,隐藏只看名字。

参考文章

  1. AI

  2. C++图书

相关文章

笔友城堡 - 可定义的个人主页

暂无评论

评论审核已启用。您的评论可能需要一段时间后才能被显示。

none
暂无评论...