每个类控制着自己的成员初始化过程,与之类似,每个类还分别控制着其他成员对于派生类来说是否可以访问。
受保护的成员
一个类使用 protected
关键字来说明哪些是它希望与派生类分享但是 不允许被其他公共访问的成员。
- 和私有成员类似,受保护的成员对于类的用户来说是不可访问的。
- 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可以访问的。
- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员,派生类对于基类对象中的受保护成员没有任何访问特权。
class Base
{
protected:
int prot_mem;
};
class Sneaky : public Base
{
friend void clobber(Sneaky&); //可以访问Sneaky::prot_mem
friend void clobber(Base&); //不可以访问Base::prot_mem
int j; // 默认private
};
//正确,clobber可以访问Sneaky的private和protected成员
void clobber(Sneaky&){s.j = s.prot_mem = 0;}
//错误,clobber不能访问Base的protected成员
void clobber(Base&){b.prot_mem = 0;}
公有、私有和受保护继承
某个类对其继承而来的成员的访问权限首两个因素的影响:
- 在基类中该成员的访问说明符。
- 在派生类的派生列表中的访问说明符。
class Base
{
public:
void pub_mem();
protected:
int prot_mem;
private:
char priv_mem;
};
struct Pub_Derv:public Base{
//@ 正确,派生类可以访问protected成员
int f(){return prot_mem;}
//@ 错误,private成员对于派生类是不可访问的
char g(){return priv_mem;}
};
struct Priv_Derv:private Base{
//@ private 不影响派生类的访问权限
int f1()const {return prot_mem;}
};
派生类的说明符对于派生类的成员及友元能否访问直接基类的成员没有什么影响,对基类成员的访问权限只与基类中的访问说明符有关。
派生访问说明符的目的是控制派生类用户包括派生类的派生类在内对于基类成员的访问权限:
Pub_Derv d1;
Priv_Derv d2;
d1.pub_mem(); //@ 正确,pub_mem在派生类中是public的
d2.pub_mem(); //@ 错误,pub_mem在派生类中是private的
struct Derived_from_Public : public Pub_Derv{
//@ 正确,Base::prot_mem 在Pub_Derv仍然是protected的
int use_base(){return prot_mem;}
};
struct Derived_from_Private : public Priv_Derv{
//@ 错误,Base::prot_mem 在Pub_Derv仍然是private的
int use_base(){return prot_mem;}
};
派生类向基类转换的可访问性
假设 D 继承自 B:
- 只有当D公有继承B时,用户代码才能使用派生类向基类的转换,如果D继承B的方式是受保护的或者私有的,用户代码不能使用该转换。
- 不论D以何种方式继承B,D的成员函数和友元函数都能使用派生类向基类的转换,派生类向直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。
- 如果D继承B的方式是公有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;如果D继承B的方式是私有的,则不能使用。
类的设计与受保护成员
一个类可以认为有三种不同的用户:普通用户,类的实现者,派生类。
- 基类应该将接口声明为公有的。
- 对于实现部分应该分成两组:
- 一组是可供派生类访问的,应该声明为受保护的,这样派生类就能在实现自己的功能时,使用基类的这些操作和数据。
- 一组应该只由基类及其友元访问的,应该声明为私有的。
友元与继承
友元关系不能传递,同样友元关系也无法继承。
基类的友元在访问派生类的成员时不具备特殊性,类似的,派生类的友元也能随意的访问基类的成员。
class Base{
//@ 添加友元声明,其他成员与之前的版本一致
friend class Pal; //@ Pal 在访问Base的派生类时不具有特殊性
};
class Pal{
public:
int f(Base b){return b.prot_mem;} //@ 正确,Pal是Base的友元
int f2(Sneaky s){return s.j;} //@ 错误,Pal不是Sneaky的友元
//@ 对于基类的访问权限由基类本身控制,即使对于派生类的基类部分也是如此
int f3(Sneak s){return s.prot_mem;} //@ Pal是Base的友元
};
Pal
是 Base
的友元,所以 Pal
能够访问 Base
对象的成员,这种访问性包括了 Base
对象内嵌在其派生类对象中的情况。
class D2 :public Pal
{
public:
int mem(Base b){return b.prot_mem;} //@ 错误,友元关系不能继承
}
改变个别成员的可访问性
通过 using
声明可以改变派生类继承的某个名字的访问级别:
class Base{
public:
std::size_t size() const {return n;}
protected:
std::size_t n;
};
//@ 使用私有继承
class Derived : private Base{
public:
using Base::size;
protected:
using Base::n;
}
因为 Derived
使用了私有继承,所以继承而来的成员 size
和 n
在默认情况下是 Derived
的私有成员,但是使用 using
声明语句改变了这些成员的可访问性,改变之后,Derived
的用户可以使用 size
成员,Derived
的派生类能使用 n
。
通过在类的内部使用 using
声明语句,可以将类的直接基类或间接基类中的任何可访问成员标记出来,using
声明语句中的名字的访问权限由该 using
声明语句前的访问说明符来决定。
注意:
派生类只能为那些它可以访问的名字提供 using
声明。
默认的继承保护级别
struct
和 class
的关键字定义的类具有不同的默认访问说明符,类似的派生运算符也由定义派生类所使用的关键字来决定。默认情况下,使用 class
关键字定义的派生类都是私有访问权限,使用 struct
定义的派生类都是公有访问权限。
class Base{/*...*/};
struct D1 : Base{/*...*/}; //@ 默认public继承
class D2 : Base{/*...*/}; //@ 默认private继承