此代码示例将描述我认为非直观的语言功能。
class A {
public:
A() {}
};
class B: private A
{
public:
B() {}
};
class C: public B
{
public:
C() {}
void ProcessA(A* a) {
}
};
int main() {
C c;
}
在Mac上使用Apple LLVM 4.2版编译此代码会产生
test.cc:16: error: ‘class A’ is inaccessible
test.cc:16: error: within this context
用void ProcessA(A* a)
替换void ProcessA(::A* a)
会使其成为现状,但我不明白为什么我应该在这里使用绝对类名。
它是一种语言功能,可以避免某些错误,或者只是一个黑暗的C ++语法角落,比如要求在与其他模板参数化的模板中的尖括号(> >
)之间放置空格。
谢谢!
答案 0 :(得分:11)
我将从描述这里发生的事情开始 - 请原谅我,如果你已经知道这一点,但它为后续行动创造了必要的背景。
编译器将非限定A
解析为::C::A
(如果您自己在源级别进行更改,结果将相同)。由于::C::A
无法访问,因此会发出错误消息。
您建议编译器检测到::C::A
无法访问,然后应将对A
的引用视为对::A
的引用作为后备。但是,::C::A
和::A
可能很容易就是两个完全不同的东西。
自动猜测这里应该做什么不仅容易引入错误和/或拔毛¹,而且完全违背C ++的精神。
直接从C ++ 11标准确认此行为符合要求并按设计进行。
§9/ 2说:
将类名插入声明它的作用域中 在看到 class-name 之后立即执行。 类名也是 插入到类本身的范围内;这被称为 注射类名
这意味着在C
类的范围内,A
是注入类名。
§3.4/ 3声明 inject-class-name 是名称查找的候选者:
类的注入类名也被视为成员 用于名称隐藏和查找的那个类。
§3.4/ 1澄清了基础A
的不可访问性并未阻止注入类名 A
被考虑:
访问规则仅被视为名称查找和功能 重载决议(如果适用)已成功。
§11.1/ 5直接解释了正在讨论的确切情况:
[注意:在派生类中,将查找基类名称的查找 inject-class-name 而不是基类的名称 声明的范围。 注入类名可能更少 比它所在范围内的基类名称可访问 被宣布。 - 后注]
该标准也给出了这个例子,它等同于你的例子:
class A { };
class B : private A { };
class C : public B {
A *p; // error: injected-class-name A is inaccessible
::A *q; // OK
};
¹想象一下,如果A
最初是public
基础,后来在重构期间变为private
会发生什么。还要设想::A
和::C::A
是无关的。你会发现像a->foo()
这样的调用(曾经工作过)会失败,因为foo
不再可访问,但是a
的类型已经改变了你的背后而你现在得到“没有方法foo
”错误。咦?!?这当然远非可能发生的最坏情况。