更改继承成员的访问级别
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
成员改为 protected
或 public
,因为派生类无权访问基类的 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_value
在 Derived
中为 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
子对象。