我想了解当使用和/或省略template
和this
关键字的4种组合时,访问修饰符关于继承的4种不同行为。以下所有代码都是在g++ 4.8中完成的:
这是GrandChild
类,private
ly继承自Parent
,private
ly继承自GrandParent
,public
拥有enum
n
GrandParent::n
。非对象客户端代码可以访问public
,因为后者是enum
GrandParent::n
。但是从GrandChild
内部无法访问GrandParent::n
:
#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.)是不是
GrandChild
拥有GrandChild
基本子对象,从而导致GrandParent
内部无法访问GrandParent::num
,而该子对象隐藏了对private
的非对象访问权限,并且其2代n
的性使得基本子对象的GrandParent
也无法访问?我希望该错误消息与之有关。2)但显然不是。为什么错误会提示
this->
的构造函数?3.)在
GrandParent::n
的定义中的f()
之前添加this->
将添加我在#1中预期的错误,但不会删除ctor投诉。为什么?我假设包含n
是多余的,并且忽略它会导致查找尝试在ont-erangely较近范围的非对象GrandParent
之前在GrandChild
的范围内查找n
子对象的this->
。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.)在
bar<N - 2>::num
的定义中在g()
之前添加ojit_code会导致仅在#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
。也就是说,查找先搜索GrandChild
的范围(找不到名称),然后搜索Parent
的范围(找不到名称),最后搜索GrandParent
的范围(在其中找到注入(inject)的类名称)。这是在访问检查之前完成的,并且与访问检查无关。找到名称
GrandParent
后,将最终检查可访问性。从Parent
到GrandParent
进行名称查找所需的名称查找。除了Parent
的成员和 friend 之外,该路径被禁止,因为继承是私有(private)的。 (您可以看到该路径,但不能使用它;可见性和可访问性是正交的概念。)这是标准的[basic.lookup.unqual]/8:
基类中的名称查找相当复杂,因为可能必须考虑多个基类。对于单一继承并在成员函数体的范围内查找成员的情况,它从该函数所属的类开始,然后向上遍历基类(base,base的base,base的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:也就是说,
bar<N - 2>
会找到bar
作为当前类(实例化)的注入(inject)类名称。当它与template-argument-list一起使用时,它指的是bar
的另一种不相关的特化。基类的注入(inject)类名称被隐藏。bar<0>::num
的访问不是通过私有(private)继承的访问路径进行的,而是通过当前类的注入(inject)的类名直接进行的,引用类模板本身。可以访问num
作为bar<0>
的公共(public)成员。