关键点是protected
授予您访问您自己的成员副本的权限,而不是访问任何其他对象中的那些成员。这是一个常见的误解,因为我们通常会概括和状态protected
授予对派生类型的成员的访问权限(没有明确说明仅对他们自己的基类......)
现在,这是有原因的,通常您不应该访问层次结构的不同分支中的成员,因为您可能会破坏其他对象所依赖的不变量。考虑一种对某些大型数据成员(受保护的)执行昂贵计算的类型和两种按照不同策略缓存结果的派生类型:
class base {
protected:
LargeData data;
// ...
public:
virtual int result() const; // expensive calculation
virtual void modify(); // modifies data
};
class cache_on_read : base {
private:
mutable bool cached;
mutable int cache_value;
// ...
virtual int result() const {
if (cached) return cache_value;
cache_value = base::result();
cached = true;
}
virtual void modify() {
cached = false;
base::modify();
}
};
class cache_on_write : base {
int result_value;
virtual int result() const {
return result_value;
}
virtual void modify() {
base::modify();
result_value = base::result();
}
};
该cache_on_read
类型捕获对数据的修改并将结果标记为无效,以便重新计算下一次读取的值。如果写入次数相对较多,这是一个很好的方法,因为我们只按需执行计算(即多次修改不会触发重新计算)。cache_on_write
预先计算结果,如果写入次数很少,并且您希望读取的成本具有确定性(考虑读取的低延迟),这可能是一个很好的策略。
现在,回到最初的问题。两种缓存策略都维护一组比基础更严格的不变量。在第一种情况下,额外的不变量是cached
只有true
在data
最后一次读取后没有被修改。在第二种情况下,额外的不变量是result_value
操作始终的值。
如果第三个派生类型引用了 abase
并且可以data
写入(如果protected
允许的话),那么它会破坏派生类型的不变量。
话虽如此,语言的规范被破坏了(个人观点),因为它留下了后门来实现特定的结果。特别是,如果您从派生类型中的基类创建指向成员成员的指针,则访问被签入derived
,但返回的指针是指向成员的指针base
,它可以应用于任何 base
对象:
class base {
protected:
int x;
};
struct derived : base {
static void modify( base& b ) {
// b.x = 5; // error!
b.*(&derived::x) = 5; // allowed ?!?!?!
}
}