13

此代码示例将描述我认为不直观的语言功能。

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++ 语法角落,比如要求> >在使用其他模板参数化的模板中的尖括号 ( ) 之间放置空格。谢谢!

4

1 回答 1

11

人话

我将首先描述这里发生的事情——如果您已经知道这一点,请原谅我,但它为后续行动创造了必要的背景。

编译器将不合格解析A::C::A(如果您自己在源代码级别进行更改,结果将相同)。由于::C::A无法访问,因此会发出错误消息。

您建议编译器应该检测到::C::A不可访问的内容,A然后应该将对的引用视为::A作为后备​​的引用。但是,::C::A::A可能很容易是两个完全不同的东西

在这里自动猜测应该做什么不仅容易引入错误和/或拉扯头发¹,而且完全违背了 C++ 的精神。

标准话

直接从 C++11 标准确认此行为符合设计要求。

§9/2 说:

名被插入到在看到类名之后立即声明它的作用域中。类也被插入到类本身的范围内;这被称为 注入类名

这意味着在 class 的范围内CA是一个注入的类名

§3.4/3 指出注入的类名是名称查找的候选者:

出于名称隐藏和查找的目的,类的注入类名称也被认为是该类的成员。

§3.4/1 阐明了基础的不可访问性A并不妨碍考虑注入的类名 A

仅在名称查找和函数重载解析(如果适用)成功后才考虑访问规则。

§11.1/5 直接解释了讨论中的确切情况:

[注意:在派生类中,基类名称的查找将在声明它的范围内找到注入的类名称,而不是基类的名称。在声明它的范围内,注入的类名可能比基类的名称更难访问。——尾注]

该标准还给出了这个例子,它相当于你的:

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”错误。啊?!?这当然远不是可能发生的最坏情况。

于 2013-03-11T15:34:23.260 回答