问题
下面一段小程序的报错:
#include <iostream>
using namespace std;
class Base {
public:
explicit Base(int x):x(x){};
int x;
};
class Derived : Base {
public:
Derived():Base(10), y(1) {
cout << "Derived construct." << endl;
};
int y;
};
int main()
{
Derived derived;
Base *base = &derived;
return 0;
}
报错如下:
D:\CLionProjects\Demo\demo.cpp: In function ‘int main()’:
D:\CLionProjects\Demo\demo.cpp:21:19: error: ‘Base’ is an inaccessible base of ‘Derived’
Base *base = &derived;
先说原因:
首先先看编译报错:Base 是Derived的一个不可访问的基类,也就是Derived的对象derived中包含的Base对象是私有的(c++中默认的继承关系是private的,类中的成员默认也是private),那么按照private的设计,derived的对象是无法访问Base的成员的,那么再将父类的指针指向子类的对象,父类指针是不能做任何操作的(均为非法的)。
修改:
将继承关系改为public继承,编译通过。
疑问及延伸问题点
- c++的对象模型中是否包含private、protected、public这些访问修饰符的信息?
- c++是如何保证访问修饰符的行为正确的落实下去?
- 这样设计的原因是什么呢?
c++的对象模型中是否包含private、protected、public这些访问修饰符的信息
在上一篇子类初始化列表不能初始化父类元素也大致描述了对象模型,如下:
可以看到子类的对象中首先包含了父类对象的内容,那么在内存模型中是否存放了private这些访问修饰符的信息呢(或者private的变量直接创建到内存中);我们来看下不同继承关系下子类对象的大小的情况:
private 继承
// 按照上面的例子做些许修改,定义类时加上alignas(4),保证是4字节对齐。
class alignas(4) Base {
public:
explicit Base(int x):x(x){};
int x;
};
class alignas(4) Derived : Base {
public:
Derived():Base(10), y(1){
cout << "Derived construct." << endl;
};
int y;
};
int main()
{
Derived derived;
//Base *base = &derived;
cout << "derived's size: " << sizeof(derived) << endl;
return 0;
}
输出
Derived construct.
derived’s size: 8
将class alignas(4) Derived : Base 修改为class alignas(4) Derived : public Base
输出
Derived construct.
derived’s size: 8
输出相同,则private和public不会对不同访问修饰符的变量进行裁剪;根据对象的大小为8:= sizeof(x) + sizeof(y),那么不管访问修饰符是什么,都会创建到c++的对象内存模型中;
那么答案也就是:c++的对象模型跟访问修饰符无关,都会将成员创建到对应的内存中去。
c++是如何保证访问修饰符的行为正确的落实下去
是否通过内存模型来进行保证了,其实上面已经说明了,没关系;大家还可以看下下面的一个有趣的例子(辅助证明无关)
class Test {
private:
int x = 2;
int y = 3;
};
int main()
{
Test t;
cout << "t.x: " << *((int *)(&t)) << endl;
cout << "t.y: " << *(((int *)(&t))+1) << endl;
}
输出:
t.x: 2
t.y: 3
哈哈,我们可以访问private变量了;侧面说明,通过内存直接访问是不受访问修饰符影响的;
那它受什么影响(或者说保障)呢?
语言设计如此(C++的保护机制);既然对象内存模型没有区分访问修饰符,语言设计又是如此,那么如何保障呢,编译器,在编译阶段,指定规则使之满足语言设计的要求。
这样设计的原因是什么呢
Bjarne在设计带类的C时候,他在设计带类的C之前,一直在操作系统领域工作,来自剑桥CAP计算机和其他类似系统的保护概念–而不是程序设计语言方面的任何工作–激发产生了C++的保护机制。
在《The Design and Evolution of C++ 》中介绍,这是C++的保护机制,类被作为保护的单位,基本规则是你不能授予自己访问一个类的权力,只有安放在类声明内部的声明可以授权你访问权。按照默认方式,所有信息都是私有的。
访问权的授予方式就是在类声明的公用部分里声明一个成员,或是把某个特定函数或者类声明为一个friend。
class X {
/*representation*/
public:
void f(); /*member function with access to representation*/
friend void g(); /*global function with access to representation*/
};
C++的保护概念如下:
- 保护是通过编译时的机制提供的,目标是防止发生意外事件,而不是防止欺骗或者有意的侵犯。(上面也分析了,保护不影响对象的布局结构, 如果欺骗上面那个有趣的例子也有表述)
- 访问权由类本身授予,而不是单方面的取用。
- 访问权控制是通过名字实行的,并不依赖于被命名事物的种类。
- **保护的单位是类,而不是个别的对象。**保护系统的职责就是保证所有违反类型系统的操作都只能显式地进行,并且尽量减少这类操作的必要性。
- 受控制的是访问权,而不是可见性。
关于第5点可以看如下代码:
int a; // global a
class X {
private:
int a; // member X::a
};
class XX : public X {
void f() { a = 1;} // which a ?
};
如果可见性是受控的,那么,因为X::a在这里是不可见的,XX::f()就会去引用全局的a。实际上带类的C和C++都认为全局的a被不可访问的X::a遮蔽了,因此XX::f()将产生一个编译错误,因为它企图去访问一个不可访问的变量X::a。1. c++的对象模型中是否包含private、protected、public这些访问修饰符的信息?
2. c++是如何保证访问修饰符的行为正确的落实下去?
3. 这样设计的原因是什么呢?
C++的访问控制是为了防止意外事件而不是防止欺骗。任何程序设计语言,只要它支持对原始存储器的访问,就会使数据处于一种开放的状态,使所有有意按照某种违反数据项原本类型规则所描述的方式去触动它的企图都能够实行。
保护系统的职责就是保证所有违反类型系统的操作都只能显式地进行,并且尽量减少这类操作的必要性。
访问修饰符效果
Keyword | C# | C++ | Java |
---|---|---|---|
‘’‘private ’’’ | class | class’‘and/or’'friend classes | class |
‘’‘private protected ’’’ | derived classes in the same assembly | - | - |
‘’‘protected internal ’’’ | same assembly ’‘and/or’' derived classes | - | - |
‘’‘protected ’’’ | derived classes | derived classes’‘and/or’'friend classes | derived classes ’‘and/or’' within same package |
‘’‘package ’’’ | - | - | within its package |
‘’‘internal ’’’ | same assembly | - | - |
‘’‘public ’’’ | everybody | everybody | everybody |
继承时:
Access specifier in base class | Access specifier when inherited publicly |
---|---|
Public | Public |
Private | Inaccessible |
Protected | Protected |
Access specifier in base class | Access specifier when inherited privately |
---|---|
Public | Private |
Private | Inaccessible |
Protected | Private |
Access specifier in base class | Access specifier when inherited protectedly |
---|---|
Public | Protected |
Private | Inaccessible |
Protected | Protected |