为什么要编译:
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(Foo& fooBar)
{
fooBar.fooBase();
}
};
但这不是吗?
class FooBase
{
protected:
void fooBase(void);
};
class Foo : public FooBase
{
public:
void foo(FooBase& fooBar)
{
fooBar.fooBase();
}
};
一方面,C ++为该类的所有实例授予对私有/受保护成员的访问权限,但另一方面,它不授予对所有子类实例的基类的受保护成员的访问权限。 这看起来与我不一致。
我已经使用VC ++和ideone.com测试了编译,并且编译了第一个但不是第二个代码片段。
答案 0 :(得分:30)
当foo
收到FooBase
引用时,编译器不知道参数是否是Foo
的后代,所以它必须假设它不是。 Foo
可以访问其他Foo
个对象的继承受保护成员,而不是所有其他同级类。
考虑以下代码:
class FooSibling: public FooBase { };
FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?
如果Foo::foo
可以调用任意FooBase
后代的受保护成员,那么它可以调用FooSibling
的受保护方法,该方法与Foo
没有直接关系。这不是保护访问应该如何工作。
如果Foo
需要访问所有FooBase
个对象的受保护成员,而不仅仅是那些也称为Foo
后代的对象,则Foo
需要成为朋友FooBase
:
class FooBase
{
protected:
void fooBase(void);
friend class Foo;
};
答案 1 :(得分:20)
C++ FAQ很好地总结了这个问题:
[你]可以自己掏腰包,但不允许你挑选父亲的口袋和兄弟的口袋。
答案 2 :(得分:10)
关键是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
始终是操作的值。
如果第三个派生类型引用base
并访问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 ?!?!?!
}
}
答案 3 :(得分:3)
在两个示例中,Foo
都会继承受保护的方法fooBase
。但是,在第一个示例中,您尝试从同一个类(Foo::foo
调用Foo::fooBase
)访问给定的受保护方法,而在第二个示例中,您尝试从另一个类中访问受保护的方法t声明为朋友类(Foo::foo
尝试调用FooBase::fooBase
,失败,后者受到保护)。
答案 4 :(得分:1)
在第一个示例中,您传递了一个Foo类型的对象,它显然继承了fooBase()方法,因此可以调用它。在第二个示例中,您试图调用受保护的函数,只是这样,无论在哪个上下文中,您都无法从声明它的类实例中调用受保护的函数。 在第一个示例中,您继承了受保护的方法fooBase,因此您有权将其称为WITHIN Foo上下文
答案 5 :(得分:1)
我倾向于从概念和信息方面看问题。如果你的FooBase方法实际上被称为“SendMessage”而Foo是“EnglishSpeakingPerson”而FooBase是SpeakingPerson,那么你的 protected 声明旨在将SendMessage限制在EnglishSpeakingPersons(和子类之间,例如:AmericanEnglishSpeakingPerson,AustralianEnglishSpeakingPerson)。源自SpeakingPerson的另一种类型FrenchSpeakingPerson将无法接收SendMessage,除非您将FrenchSpeakingPerson声明为朋友,其中'friend'意味着FrenchSpeakingPerson具有从EnglishSpeakingPerson接收SendMessage的特殊能力(即可以理解英语)。
答案 6 :(得分:0)
你可以在没有朋友的情况下工作......
class FooBase
{
protected:
void fooBase(void);
static void fooBase(FooBase *pFooBase) { pFooBase->fooBase(); }
};
这避免了必须向基类添加派生类型。这似乎有点循环。