隐藏继承而来的功能

更改继承成员的访问级别

C++ 允许在派生类中改变继承成员的访问说明符。做法是在派生类中,用一条 using 声明指出(带作用域的)基类成员,并将其置于新的访问说明符之下。

示例:给定如下基类

#include <iostream>

class Base
{
private:
    int m_value {};

public:
    Base(int value)
        : m_value { value }
    {
    }

protected:
    void printValue() const { std::cout << m_value; }
};

由于 Base::printValue() 被声明为 protected,只能由 Base 及其派生类调用,外界无法访问。

现在定义一个派生类,将 printValue() 的访问级别改为 public

class Derived: public Base
{
public:
    Derived(int value)
        : Base { value }
    {
    }

    // Base::printValue 原继承为 protected,外界不可见
    // 通过 using 声明将其改为 public
    using Base::printValue; // 注意:此处不加括号
};

于是以下代码可以正常编译运行:

int main()
{
    Derived derived { 7 };

    // printValue 在 Derived 中为 public,故可调用
    derived.printValue(); // 输出 7
    return 0;
}

只能更改派生类原本就能访问的基类成员的访问级别。因此,永远不能将基类的 private 成员改为 protectedpublic,因为派生类无权访问基类的 private 成员。

隐藏功能

在 C++ 中,除了修改源码外,无法从基类真正“移除”或“限制”功能。然而,在派生类中可以通过调整访问说明符来隐藏基类功能,使之无法通过派生类对象访问。

例如,可将基类的 public 成员改为 private

#include <iostream>

class Base
{
public:
    int m_value {};
};

class Derived : public Base
{
private:
    using Base::m_value;   // 隐藏为 private

public:
    Derived(int value) : Base { value }
    {
    }
};

int main()
{
    Derived derived{ 7 };
    std::cout << derived.m_value; // 错误:m_value 在 Derived 中为 private

    Base& base{ derived };
    std::cout << base.m_value;    // 合法:m_value 在 Base 中仍为 public

    return 0;
}

上述技巧使我们能够对一个设计欠佳的基类进行封装。
另一种做法是直接对基类使用私有继承,这样所有基类成员在派生类中默认成为 private,无需逐一修改。

需要强调的是:尽管 m_valueDerived 中为 private,但在 Base 中仍为 public。因此,通过将 Derived 对象强制转换为 Base& 并直接访问成员,仍可绕过 Derived 的封装。

【进阶阅读】同理,若基类含有 public virtual 函数,而派生类将其改为 private,外界仍可将派生类对象转换为 Base& 并调用该虚函数。编译器允许此操作,因为在 Base 中该函数为 public;运行时根据虚函数机制仍会分派到(已变为 private 的)派生类版本。访问控制在运行期并不生效。

示例:

#include <iostream>

class A
{
public:
    virtual void fun()
    {
        std::cout << "public A::fun()\n";
    }
};

class B : public A
{
private:
    virtual void fun()
    {
        std::cout << "private B::fun()\n";
    }
};

int main()
{
    B b {};
    b.fun();                  // 编译错误:B::fun() 为 private
    static_cast<A&>(b).fun(); // 合法:A::fun() 为 public,运行时调用 private B::fun()

    return 0;
}

无法针对单个重载单独修改访问级别

若基类的一组重载函数同名,你无法只改变其中某一个的访问级别;只能全部一起改变:

#include <iostream>

class Base
{
public:
    int m_value {};

    int getValue() const { return m_value; }
    int getValue(int) const { return m_value; }
};

class Derived : public Base
{
private:
    using Base::getValue; // 所有 getValue 函数均被设为 private

public:
    Derived(int value) : Base { value }
    {
    }
};

int main()
{
    Derived derived{ 7 };
    std::cout << derived.getValue();   // 错误:getValue() 在 Derived 中为 private
    std::cout << derived.getValue(5); // 错误:getValue(int) 在 Derived 中为 private

    return 0;
}

在派生类中删除函数

亦可在派生类中将成员函数标记为 = delete,从而禁止通过派生类对象调用该函数:

#include <iostream>

class Base
{
private:
    int m_value {};

public:
    Base(int value)
        : m_value { value }
    {
    }

    int getValue() const { return m_value; }
};

class Derived : public Base
{
public:
    Derived(int value)
        : Base { value }
    {
    }

    int getValue() const = delete; // 删除该函数
};

int main()
{
    Derived derived { 7 };

    // 以下语句无法通过编译,因为 getValue() 已被删除
    std::cout << derived.getValue();

    return 0;
}

上例中,派生类的 getValue() 被删除,编译器会在调用时报错。注意,基类的 getValue() 依然可访问。有两种方式可以显式调用基类版本:

int main()
{
    Derived derived { 7 };

    // 直接调用 Base::getValue()
    std::cout << derived.Base::getValue();

    // 或将 Derived 向上转型为 Base& 后调用
    std::cout << static_cast<Base&>(derived).getValue();

    return 0;
}

若使用强制类型转换,应将对象转型为 Base& 而非 Base,以避免复制 Derived 中的 Base 子对象。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"