gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:35:00 25 4
gpt4 key购买 nike

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

这是一个 GrandChild类,private ly 继承自 Parent ,这private ly 继承自 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.
}

1.) 是 GrandParent::n从内部无法访问 GrandChildGrandChild 引起拥有一个GrandParent基础子对象,它隐藏了对 GrandParent::num 的非对象访问, 以及其 2 代 private ness 使基础子对象的 n也无法访问?我原以为错误消息与此有关。

2.) 但显然,事实并非如此。为什么报错GrandParent的构造函数?

3.) 前置this->GrandParent::nf()的定义将添加我在 #1 中预期的错误,但不会删除 ctor 投诉。 为什么? 我假设包括 this->是多余的,它的遗漏将导致查找试图找到 nGrandParent GrandChild 中的子对象在 less-immediately-scoped 非对象之前的范围 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
}

5.) 前置this->bar<N - 2>::numg()的定义只导致我在#1 中预期的编译器错误。但是为什么它不包含#2 的错误呢?为什么它的遗漏不会产生 #2 的错误?

最佳答案

这里的整个问题是名称查找(我认为情况也是如此 in one of your previous questions )。我将尝试说明我对正在发生的事情的理解:

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

struct GrandParent
{
// using GrandParent = ::GrandParent;
enum {n = 0};
};

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

namespace A
{
struct Foo
{
// using Foo = ::A::Foo;
};
};

struct Bar : A::Foo
{
void woof(Foo); // using the injected-class-name `::A::Foo::Foo`
};

template<class T, int N, bool b>
struct my_template
{
// using my_template = ::my_template<T, N, b>;
void meow(my_template); // using the injected-class-name
};

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

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

struct Parent : private GrandParent
{
// using Parent = ::Parent;

enum {n = 1}; // hides GrandParent::n
};

struct GrandChild : private Parent {
// using GrandChild = ::GrandChild;

enum {n = 2};
void f() {cout << GrandParent::n << endl;}
// ^ error: 'struct GrandParent GrandParent::GrandParent'
// is inaccessible
};

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

名字后GrandParent已找到,最终检查可访问性。需要从 Parent 进行名称查找至 GrandParent找到名字。除 Parent 的成员和 friend 外,此路径对任何人都被阻止,因为继承是私有(private)的。 (您可以看穿那条路径,但您可能不会使用它;可见性和可访问性是正交的概念。)


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

For the members of a class X, a name used in a member function body [...] shall be declared in one of the following ways:

  • before its use in the block in which it is used or in an enclosing block, or
  • shall be a member of class X or be a member of a base class of X, or
  • if X is a nested class of class Y [...]
  • [...]
  • if X is a member of namespace N, or [...], before the use of the name, in namespace N or in one of N’s enclosing namespaces.

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


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

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;
}
};

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

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

Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected- class-name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type- specifier of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in <>.

bar<N - 2>发现 bar作为当前类的注入(inject)类名(实例化)。因为它与 template-argument-list 一起使用,所以它指的是 bar 的另一个不相关的特化。 .基类的注入(inject)类名是隐藏的。

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

关于c++ - 访问修饰符在继承中的不同行为取决于 "this"关键字和模板或缺少它们,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20808993/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com