关于使用和/或省略template
s和this
关键字的4种组合,我想了解访问修饰符有关继承的4种不同行为。以下所有代码均在g ++ 4.8中完成:
这是一个GrandChild
类,private
继承自Parent
,private
继承自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
拥有GrandChild
基础子对象导致GrandChild
GrandParent
内部GrandParent::num
无法访问,该子对象隐藏了对象的非对象访问权限{ {1}},其2代private
使得基础子对象的n
也无法访问?我希望错误信息是关于那个。
2。)但显然,事实并非如此。为什么错误会抱怨GrandParent
的构造函数?
3。)在this->
的定义中预先GrandParent::n
到f()
会添加我在#1中预期的错误,但不会删除ctor投诉。 为什么?我认为包含this->
是多余的,而且遗漏会导致查找尝试在{{1}内找到n
子对象的GrandParent
无论如何,在范围较小的非对象GrandChild
之前的范围。
4.。)为什么这个模板变体会编译?它看起来在功能上与非模板类似:
n
5.在#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
}
的定义中将this->
前置到bar<N - 2>::num
会导致我在#1中预期的编译器错误。但为什么不包括#2的错误?为什么它的遗漏没有产生#2的错误?
答案 0 :(得分:3)
这里的整个问题是名称查找(我认为情况也是如此in one of your previous questions)。我会试着说明我对正在发生的事情的理解:
每个(命名)类都会获得一个注入类名。例如:
struct GrandParent
{
// using GrandParent = ::GrandParent;
enum {n = 0};
};
您可以使用此inject-class-name来引用类本身。它对普通类没有用(其中非限定查找无论如何都可以在周围的范围中找到名称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.unqual] / 8:
对于类
X
的成员,成员函数体[...]中使用的名称应以下列方式之一声明:
- 在用于使用它的块中或在封闭块中使用之前,或
- 应为班级
X
的成员,或者是X
基类的成员,或- 如果
的嵌套类X
是类Y
[...]- [...]
- 如果
X
是名称空间N
的成员,或[...],则在使用名称之前, 在名称空间N
或N
的一个名称空间中。
基类中的名称查找相当复杂,因为可能必须考虑多个基类。对于单继承和在成员函数体范围内查找的成员,它从该函数所属的类开始,然后遍历基类(基数,基数,基数的基数, ..)。见[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-name-name可以用作模板名称(指类模板)或类型名称(指当前实例化)[temp.local] / 1 :
与普通(非模板)类一样,类模板具有注入类名(第9节)。注入 - class-name可用作模板名称或类型名称。当它与 template-argument-list 一起使用时, 作为模板模板参数的模板参数,或作为详细说明类型中的最终标识符 - 友元类模板声明的说明符,它指的是类模板本身。否则,它是等价的 到 template-name 后跟
<>
中包含的类模板的 template-parameters 。
也就是说,bar<N - 2>
找到bar
作为当前类(实例化)的注入类名。由于它与 template-argument-list 一起使用,因此它引用了bar
的另一个不相关的特化。隐藏了基类的inject-class-name。
bar<0>::num
通过访问路径访问而不是,该访问路径通过私有继承,但直接通过当前类的inject-class-name引用类模板本身。 num
是bar<0>
的公共成员,可以访问。
答案 1 :(得分:1)
为了更好地解释为什么私有继承不像公共继承和受保护的继承,请参阅private inheritance中的两个好答案。
从对继承的共同理解,C ++'“私有继承” 这是一个可怕的用词:它不是继承(就所有事情而言) 在课外有关)但是完整的实施 班上的细节。
从外部看,私人继承实际上就是这样 与作文相同。只有在班级内部才能得到 比继承更容易让人想起继承的特殊语法 组合物。
但有一点需要注意:C ++在语法上将其视为继承, 具有所需的所有好处和问题,例如范围 可见性和可访问性。此外,C风格的演员阵容(但没有C ++ 演员!)实际上忽略了可见性,从而成功地投射你的 派生指向Base的指针:
Base* bPtr = (Base*) new Derived();
毋庸置疑,这是邪恶的。
公共继承意味着每个人都知道Derived是派生的 来自Base。
受保护的继承意味着只有Derived,Derived的朋友和 派生自Derived的类知道Derived派生自Base。*
私有继承意味着只有Derived和Derived的朋友 知道Derived是从Base。派生的。
由于您使用了私有继承,因此main()函数没有 关于从base派生的线索,因此无法分配指针。
私有继承通常用于实现 “与......有关的实施”。一个例子可能是 Base公开了一个你需要覆盖的虚函数 - 因此 必须继承 - 但你不希望客户知道你 有继承关系。
以及Inaccessible type due to private inheritance。
这是由于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
纳入范围。