访问修饰符在继承中的不同行为取决于"this"关键字和模板或缺少

Cod*_*cks 5 c++ inheritance templates access-modifiers this

我想了解访问修饰符关于继承的4种不同行为,当涉及使用和/或省略templates和this关键字的4种组合时.以下所有代码均在g ++ 4.8中完成:

这里有一个GrandChild类,它privateLY从继承Parent,这privateLY从继承GrandParent,其中有一个public enum n.非对象,客户端代码可以访问GrandParent::n,因为后者是一个public enum.但是GrandParent::n从内部无法进入GrandChild:

#include <iostream>
using namespace std;

struct GrandParent { enum {n = 0}; };

struct Parent : private GrandParent { enum {n = 1}; };

struct GrandChild : private Parent {
    enum {n = 2};
    void f() {cout << GrandParent::n << endl;}
    // ^ error: 'struct GrandParent GrandParent::GrandParent'
    // is inaccessible
};
int main() {
    cout << GrandParent::n << endl;
    // ^ non-object access would have outputted `0` had `GrandChild`'s
    // definition compiled or been commented out.
}
Run Code Online (Sandbox Code Playgroud)

1.)由于拥有一个隐藏非对象访问权限的基础子对象而导致GrandParent::n内部不可访问,并且其2代代理使得基础子对象也无法访问?我期望错误信息是关于那个.GrandChildGrandChildGrandParentGrandParent::numprivaten

2.)但显然,事实并非如此.为什么错误会抱怨GrandParent构造函数?

3)前面加上this->GrandParent::nf()的定义会增加我的预期误差在1号,但不会删除构造函数投诉.为什么?我认为,包括this->是多余的,它的不作为将导致查找,以试图找到n对的GrandParent子对象内GrandChild的,立即范围的扣除非对象之前的范围n反正.

4.)为什么这个模板变体编译?它看起来在功能上与非模板类似:

#include <iostream>
using namespace std;

template <unsigned int N>
struct bar : private bar<N - 1> {
    enum {num = N};
    void g() {
        static_assert(N >= 2, "range error");
        cout << bar<N - 2>::num << endl;
    }
};

template <>
struct bar<0> { enum {num = 0}; };

int main() {
    bar<2> b2;
    b2.g(); // Output: 0
}
Run Code Online (Sandbox Code Playgroud)

5.)this->bar<N - 2>::numin g()的定义中添加前导致我在#1中预期的编译器错误.但为什么不包括#2的错误?为什么它的遗漏不会产生#2的错误?

dyp*_*dyp 3

这里的整个问题是名称查找(我认为您之前的问题之一也是这种情况)。我将尝试说明我对正在发生的事情的理解:

\n\n

每个(命名的)类都有一个注入类名。例如:

\n\n
struct GrandParent\n{\n    // using GrandParent = ::GrandParent;\n    enum {n = 0};\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以使用这个注入类名来引用类本身。GrandParent它对于普通类(其中非限定查找无论如何都可以在周围范围中找到名称)没有那么有用,但对于派生类和类模板来说:

\n\n
namespace A\n{\n    struct Foo\n    {\n        // using Foo = ::A::Foo;\n    };\n};\n\nstruct Bar : A::Foo\n{\n    void woof(Foo); // using the injected-class-name `::A::Foo::Foo`\n};\n\ntemplate<class T, int N, bool b>\nstruct my_template\n{\n    // using my_template = ::my_template<T, N, b>;\n    void meow(my_template); // using the injected-class-name\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不是“它是基类子对象的一部分”中的继承,而是指定非限定查找的方式:如果在当前类范围内找不到该名称,则基类范围将被搜查。

\n\n

现在,对于OP中的第一个(非模板)示例:

\n\n
struct Parent : private GrandParent\n{\n    // using Parent = ::Parent;\n\n    enum {n = 1}; // hides GrandParent::n\n};\n\nstruct GrandChild : private Parent {\n    // using GrandChild = ::GrandChild;\n\n    enum {n = 2};\n    void f() {cout << GrandParent::n << endl;}\n    // ^ error: \'struct GrandParent GrandParent::GrandParent\'\n    // is inaccessible\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

此处,表达式GrandParent::n调用 name 的非限定名称查找GrandParent。由于当找到名称时,非限定查找会停止(并且不考虑周围范围),因此它将找到注入的类名称GrandParent::GrandParent。也就是说,lookup 搜索GrandChild(未找到名称)的范围,然后搜索(未找到名称)的范围,最后搜索(找到注入类名称的位置)Parent的范围。GrandParent这是在访问检查之前完成的并且独立于访问检查。

\n\n

GrandParent找到名称后,最终检查可访问性。名称查找需要从ParentGrandParent查找名称。Parent由于继承是私有的,因此除 的成员和朋友之外的任何人都无法访问此路径。(你可以通过这条路径看到,但你可能不会使用它;可见性和可访问性是正交的概念。)

\n\n
\n\n

这是标准的 [basic.lookup.unqual]/8:

\n\n
\n

对于类的成员X,成员函数体 [...] 中使用的名称应通过以下方式之一声明:

\n\n
    \n
  • 在使用它的块或封闭块中使用之前,或者
  • \n
  • 应该是类的成员X或者是基类的成员X,或者
  • \n
  • if是类[...]X的嵌套类Y
  • \n
  • [...]
  • \n
  • if是 命名空间或 [...]X的成员,在使用名称之前,\n 在命名空间中或在包含命名空间的 \xe2\x80\x99之一中。NNN
  • \n
\n
\n\n

基类中的名称查找相当复杂,因为可能必须考虑多个基类。对于单继承和在成员函数体范围内查找的成员,它从该函数所属的类开始,然后向上遍历基类(base、base of base、base of base of base、 ..)。请参阅[class.member.lookup]

\n\n
\n\n

模板大小写不同,bar类模板的名称也不同:

\n\n
template <unsigned int N>\nstruct bar : private bar<N - 1> {\n    enum {num = N};\n    void g() {\n        static_assert(N >= 2, "range error");\n        cout << bar<N - 2>::num << endl;\n    }\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里,bar<N - 2>使用的是。它是一个从属名称,就像N模板参数一样。因此,名称查找被推迟到 的实例化点gbar<0>即使它是在函数之后声明的,也可以找到特化。

\n\n

的注入类名称bar可以用作模板名称(指类模板)或类型名称(指当前实例化)[temp.local]/1:

\n\n
\n

与普通(非模板)类一样,类模板有一个注入类名(第 9 条)。注入的类名可以用作模板名类型名当它与template-argument-list一起使用时,\n 作为模板template-parameter模板参数,或者作为友元类模板声明的详尽类型-\n 说明符中的最终标识符,它指的是类模板本身。否则,它相当于模板名称,后跟中包含的类模板的模板参数<>

\n
\n\n

也就是说,bar<N - 2>查找当前bar类(实例化)的注入类名。由于它与template-argument-list一起使用,因此它指的是 的另一个不相关的专业化。基类的注入类名是隐藏的。bar

\n\n

bar<0>::num不是通过私有继承的访问路径来访问,而是直接通过当前类的注入类名来访问,引用类模板本身。num成为 的公共成员bar<0>是可以的。

\n