当涉及到使用和/或省略template
s和this
关键字的4种组合时,我想了解访问修饰符关于继承的4种不同行为。以下所有代码都在g++4.8中完成:
这里有一个GrandChild
类,private
ly从Parent
继承,private
ly从具有public
enum
n
的GrandParent
继承。非对象,客户端代码可以访问GrandParent::n
,因为后者是public
enum
。但从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.)GrandParent::n
在GrandChild
中的不可访问性是由GrandChild
拥有GrandParent
基子对象引起的吗?该基子对象隐藏了对GrandParent::num
的非对象访问,并且其两代private
性使基子对象的n
也不可访问?我本以为错误消息是关于这个的。
2.)但显然不是。为什么错误抱怨GrandParent
的构造函数?
3.)在f()
的定义中将this->
预处理为GrandParent::n
将添加我在#1中预期的错误,但不会删除ctor投诉为什么我假设包括this->
是多余的,并且它的省略将导致查找尝试在GrandChild
的作用域内的GrandParent
子对象的n
之前找到不太直接的作用域非对象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.)在g()
的定义中将this->
预处理为bar<N - 2>::num
只会导致我在#1中预期的编译器错误。但是为什么它不包括#2的错误呢?为什么它的遗漏没有产生#2的错误?
这里的全部问题是名称查找(我认为您之前的一个问题也是如此)。我将尝试说明我对正在发生的事情的理解:
每个(命名的)类都会得到一个注入的类名。例如:
struct GrandParent
{
// using GrandParent = ::GrandParent;
enum {n = 0};
};
您可以使用这个注入的类名来引用类本身。它对普通类没有那么有用(在普通类中,非限定查找无论如何都可以在周围的范围中找到名称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
的非限定名称查找。当找到名称时(并且不考虑周围的作用域),非限定查找停止,它将找到注入的类名GrandParent::GrandParent
。也就是说,查找搜索GrandChild
的范围(未找到名称),然后搜索Parent
的范围(找不到名称),最后搜索GrandParent
的范围(在其中查找注入的类名)。这是在和之前完成的,与访问检查无关。
在找到名称GrandParent
之后,最终检查可访问性。名称查找需要从Parent
转到GrandParent
才能找到名称。除了Parent
的成员和朋友之外,任何人都会阻止此路径,因为继承是私有的。(你可以看到这条路径,但你可能不会使用它;可见性和可访问性是正交的概念。)
这是标准的【basic.lookup.uqual】/8:
对于类
X
的成员,成员函数体[…]中使用的名称应通过以下方式之一声明:
- 在使用它的块中或在封闭块中使用之前,或
- 应为
X
类成员或X
基类成员,或- 如果
X
是类Y
[…]的嵌套类- […]
- 如果在使用名称之前
X
是名称空间N
的成员,或者[…],在命名空间CCD_ 53中或在CCD_
基类中的名称查找相当复杂,因为可能需要考虑多个基类。对于单个继承和在成员函数体范围内查找的成员,它从该函数所属的类开始,然后向上遍历基类(base、base of base、base的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
的注入类名可以用作模板名(指类模板)或类型名(指当前实例化)[temp.local]/1:
与普通(非模板)类一样,类模板有一个注入的类名(第9条)。注射的-类名可以用作模板名或类型名。当它与模板参数列表一起使用时,作为模板模板参数的模版参数,或作为阐述类型中的最终标识符-友元类模板声明的说明符,它引用类模板本身。否则,它是等效的到模板名称,然后是
<>
中包含的类模板的模版参数。
也就是说,bar<N - 2>
找到bar
作为当前类(实例化)的注入类名。当它与模板参数列表一起使用时,它引用了bar
的另一个不相关的专门化。基类的注入类名是隐藏的。
bar<0>::num
不是通过经过私有继承的访问路径访问,而是直接通过当前类的注入类名访问,引用类模板本身。作为CCD_ 67的公共成员的CCD_。
要想更好地解释为什么私有继承与公共和受保护继承不同,请参阅私有继承的两个好答案。
从继承的共同理解来看,C++的"私有继承"这是一个可怕的误称:它不是继承(就一切而言)类外),但是一个完整的实现类的详细信息。
从外表上看,私人继承实际上是与组成相同。只有在课堂内部,你才能比继承更令人想起的特殊语法作文
但有一点需要注意:C++在语法上将其视为继承,这带来的所有好处和问题,例如范围可见性和可访问性。此外,C样式强制转换(但没有C++演员阵容!)实际上忽略了可见性,因此成功地铸造了指向基的派生指针:
Base* bPtr = (Base*) new Derived();
不用说,这是邪恶的。
公共继承意味着每个人都知道Derived是派生的从基地。
受保护的继承意味着只有Derived、Derived的朋友和派生自derived的类知道derived派生自Base。*
私人继承意味着只有Derived和Derived的朋友知道Derived是从Base派生的。
由于使用了私有继承,main()函数没有关于从基派生的线索,因此无法分配指针。
私人继承通常用于实现"是根据"关系来实现的。一个例子可能是Base公开了一个需要重写的虚拟函数,因此必须继承自--但您不希望客户知道具有继承关系。
以及由于私有继承而导致的不可访问类型。
这是由于从A注入的类名隐藏了全局A内部C。虽然A是可见的,但它是不可访问的(因为它是导入为私有),因此出现错误。你可以通过查看来访问A在全局命名空间中:
void foo(::A const& a) {}
例如,这将起作用:
class GrandChild;
struct GrandParent { enum {n = 0}; };
struct Parent : private GrandParent {
enum {n = 1};
friend GrandChild;
};
struct GrandChild : private Parent {
void f() {cout << GrandParent::n << endl;}
};
否则,您需要使用全局作用域或using
指令将::GrandParent
引入作用域。